Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Ensuring the existence of objects

Memory and resource management are major concerns in C++. When you create any C++ program, you have the option of creating objects on the stack or on the heap (using new). In a single-threaded program, it s usually easy to keep track of object lifetimes so that you don t try to use objects that are already destroyed.

The examples shown in this chapter create Runnable objects on the heap using new, but you ll notice that these objects are never explicitly deleted. However, you can see from the output when you run the programs that the thread library keeps track of each task and eventually deletes it, because the destructors for the tasks are called. This happens when the Runnable::run( ) member function completes returning from run( ) indicates that the task is finished.

Burdening the thread with deleting a task is a problem. That thread doesn t necessarily know if another thread still needs to make a reference to that Runnable, and so the Runnable may be prematurely destroyed. To deal with this problem, tasks in ZThreads are automatically reference-counted by the ZThread library mechanism. A task is maintained until the reference count for that task goes to zero, at which point the task is deleted. This means that tasks must always be deleted dynamically, and so they cannot be created on the stack. Instead, tasks must always be created using new, as you see in all the examples in this chapter.

Often you must also ensure that non-task objects stay alive as long as tasks need them. Otherwise, it s easy for objects that are used by tasks to go out of scope before those tasks are completed. If this happens, the tasks will try to access illegal storage and will cause program faults. Here s a simple example:

//: C11:Incrementer.cpp {RunByHand}
// Destroying objects while threads are still
// running will cause serious problems.
//{L} ZThread
#include <iostream>
#include "zthread/Thread.h"
#include "zthread/ThreadedExecutor.h"
using namespace ZThread;
using namespace std;
 
class Count {
enum { SZ = 100 };
int n[SZ];
public:
void increment() {
for(int i = 0; i < SZ; i++)
n[i]++;
}
};
 
class Incrementer : public Runnable {
Count* count;
public:
Incrementer(Count* c) : count(c) {}
void run() {
for(int n = 100; n > 0; n--) {
Thread::sleep(250);
count->increment();
}
}
};
 
int main() {
cout << "This will cause a segmentation fault!" << endl;
Count count;
try {
Thread t0(new Incrementer(&count));
Thread t1(new Incrementer(&count));
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
} ///:~
 

The Count class may seem like overkill at first, but if n is only a single int (rather than an array), the compiler can put it into a register and that storage will still be available (albeit technically illegal) after the Count object goes out of scope. It s difficult to detect the memory violation in that case. Your results may vary depending on your compiler and operating system, but try making it n a single int and see what happens. In any event, if Count contains an array of ints as above, the compiler is forced to put it on the stack and not in a register.

Incrementer is a simple task that uses a Count object. In main( ), you can see that the Incrementer tasks are running for long enough that the Count object will go out of scope, and so the tasks try to access an object that no longer exists. This produces a program fault.

To fix the problem, we must guarantee that any objects shared between tasks will be around as long as those tasks need them. (If the objects were not shared, they could be composed directly into the task s class and thus tie their lifetime to that task.) Since we don t want the static program scope to control the lifetime of the object, we put the object on the heap. And to make sure that the object is not destroyed until there are no other objects (tasks, in this case) using it, we use reference counting.

Reference counting was explained thoroughly in volume one of this book and further revisited in this volume. The ZThread library includes a template called CountedPtr that automatically performs reference counting and deletes an object when the reference count goes to zero. Here s the above program modified to use CountedPtr to prevent the fault:

//: C11:ReferenceCounting.cpp
// A CountedPtr prevents too-early destruction.
//{L} ZThread
#include <iostream>
#include "zthread/Thread.h"
#include "zthread/CountedPtr.h"
using namespace ZThread;
using namespace std;
 
class Count {
enum { SZ = 100 };
int n[SZ];
public:
void increment() {
for(int i = 0; i < SZ; i++)
n[i]++;
}
};
 
class Incrementer : public Runnable {
CountedPtr<Count> count;
public:
Incrementer(const CountedPtr<Count>& c ) : count(c) {}
void run() {
for(int n = 100; n > 0; n--) {
Thread::sleep(250);
count->increment();
}
}
};
 
int main() {
CountedPtr<Count> count(new Count);
try {
Thread t0(new Incrementer(count));
Thread t1(new Incrementer(count));
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
} ///:~
 

Incrementer now contains a CountedPtr object, which manages a Count. In main( ), the CountedPtr objects are passed into the two Incrementer objects by value, so the copy-constructor is called, increasing the reference count. As long as the tasks are still running, the reference count will be nonzero, and so the Count object managed by the CountedPtr will not be destroyed. Only when all the tasks using the Count are completed will delete be called (automatically) on the Count object by the CountedPtr.

Whenever you have objects that are used by more than one task, you ll almost always need to manage those objects using the CountedPtr template in order to prevent problems arising from object lifetime issues.

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire