|
 |
 |
[an error occurred while processing this directive]
The sample implementation uses two classes. One object class will contain the data object (this is called the container object or class throughout this How-To), and another will abstract data access (referred to as the pointer class or object). The container keeps the reference count, and that is the actual number of data access objects currently instantiated. When a data access object falls out of scope, it notifies the container class of this event.
This technique is also an excellent candidate for using the C++ template mechanism to make this code applicable to many situations. This is best combined with a class library that has support classes that abstract thread control and management functions (these are usually portable to more than one operating system, and so make the classes here even more useful).
Steps
As mentioned above, memory management like this can become very complicated quickly. It requires very careful planning before beginning to implement the code.
- 1. Define the purpose of the memory management code. This is more than to manage memory in the program. It should be specific and concise. For larger projects, several memory management implementations might be needed to provide specific behaviors or features required for specific parts of the program. At that point, consider using inheritance models to manage common functionality.
For this How-To, the criteria were as follows:
- A. The container and pointer objects must be content-specific and type safe. Templates are used to accomplish this.
- B. The container object must clean itself up in addition to the data object it owns. It will essentially delete itself when it is no longer referred to.
- C. Criterion B would have disastrous effects if the object or container were not allocated with new. In the interest of keeping the code concise, it must be made a rule. Possible approaches that could be taken in production code are to overload the new operator, to cause it to set a flag in the container, and to throw an exception from the constructor if the object was not allocated with new.
- D. The pointer objects should overload the member-of operator of the pointer (operator->) to make accessing the members of the contained object possible.
- E. The object container class should be able to contain arrays of objects. To keep this How-To as brief as possible, the pointer class will not support arrays. Adding array support requires overloading the index-of operator, overloading the addition and subtraction operators, and maintenance of a pointer to the data within the pointer class. Its not as complicated as it sounds, but adding all this will likely cloud the basic techniques presented in this How-To.
- F. The container must protect its own management data, but no more. Imagine one thread referencing an object while the object was cleaning itself up because its reference count arrived at zero in another. The program would have a deadlock at best, and an outright program crash would be a more likely result.
- G. The container class will control the locking of the count variables though mutexes. This should allow the control of the restricted items to be kept in a central few member functions.
MULTITHREADED PROGRAMMING TERMINOLOGY
The multitasking and multithreaded operating system world has developed some terminology to describe the means to coordinate the actions of threads and processes, as well as the means of communicating between them. The general terms used to control execution in multithreaded programming are detailed in the following paragraphs.
A process is defined as a program running with its own stack and data space on a computer. Processes are given a specific allocation of processor time by the host operating system. The scheme by which time is allocated varies from system to system.
A thread is a mini process. All threads share the same data space as the process that starts them, but have their own stack space. All threads share the processor time of their process.
A semaphore is a generic term for a variable used to communicate between processes and/or threads.
A mutual exclusion semaphore (a mutex for short) is a semaphore intended to ensure only one thread can execute an area covered by the semaphore at one time. This is usually managed by the operating system. A program thread locks the semaphore, and while locked, any other thread attempting to lock the semaphore is blocked from continuing until the other code releases the semaphore. These are normally used to process data that does not fare well if another thread is working on it as well. This requires the code to attempt to lock the semaphore, and the system will not stop the program from working with the data in question without locking the semaphore. Because of the discipline required in working with mutexes, it is best to only work with a particular mutex in one or two sections of code.
- 2. Define the layout of the data container class. Depending on the purpose, it might be more useful to actually store the data object, rather than a pointer to it. The following code shows the object container class template (part of memclass.hpp):
template <class t>
class ObjectPtr ;
template <class t>
class ObjectContainer
{
private:
t* _data ;
unsigned int ArraySize ;
unsigned int Refs ;
friend ObjectPtr<t> ;
// for multithreaded environments, also include a mutex handle
// to protect the reference counter and the pointer to data.
public:
ObjectContainer( t* data, int array_size ) ;
ObjectContainer( const ObjectContainer<t> &copyfrom ) ;
ObjectContainer& operator=( const ObjectContainer<t>
⇒&copyfrom ) ;
// reference handlers
void reference() ;
void dereference() ;
// inspection functions
unsigned int getrefs() const { return Refs ; }
~ObjectContainer() ;
} ;
// end ObjectContainer class definition
Note the forward declaration of the ObjectPtr template class. This must be defined in order to allow the ObjectContainer class to have it as a friend class.
The remainder of the listing implements a memory management class as implemented in How-To 13.2. The number of references to the object contained is in the Refs variable.
|