Combining object-counting technique with the pseudo-constructors, we can limit the number of objects of a class.
Allowing zero or one objects
Using the classic singleton design pattern, it’s easy to limit the number of object to either zero or one. There are three points worth noting in this design:
- Declaring the constructors of the class
private - Using static object
- In order to access the single object, encapsulate the single object inside a accessor function (either a friend function inside some namespace/globally, or a static member function of that class). Note that:
- Remember to put the static object inside this wrapper function to make it a function static instead of a class static, because
- A class static is always constructed even if it’s never used, while a function static is created the first time through the function
- C++ says noting about the initialization order of static objects in different translation units, so class statics turn out to be a source of headaches, which can be avoided in the case of function statics (ECpp Item 4).
- If this wrapper function is also declared as
inline, it’s possible for some compilers to create more than one copy of the static objects in the program due to internal linkage (the object code for the program may contain more than one copy of each function with internal linkage, and this duplication includes function statics). So shy away from inline functions with static data.
- Remember to put the static object inside this wrapper function to make it a function static instead of a class static, because
Example:
|
|
Since the accessor returns a reference to a Printer object, clients may use thePrinter in any context where a Printer object itself is expected:
|
|
However, there’s still an inconvenience in this design: we’re limited to a single Printer object for each run of the program. As a result, it’s not possible to write code like this:
|
|
This design never instantiates more than a single Printer object at a time, but it does use different Printer objects in different parts of the program. It does not violate the constraint that only one printer may exist, but is still illegal with a single function static implementation.
This need for flexibility leads us to the design of object-counting.
Allowing multimple objects: object-counting with pseudo-constructor
Object-counting
The good point of object-counting is that, it provides us with more flexibility than the function static, and makes it easier to generalize the limit number to more than one. However, object-counting alone will not work. For example:
|
|
|
|
The problem here is that, in order to set a limit on the number of instantiations, we should not declare the class constructor public, because that will allow clients to put the class as base class parts of more derived objects, or embedded inside larger objects, which is totally different usage context, and the presence of these different contexts significantly muddies the waters regarding what it means to keep track of the “number of objects in existence.” For example:
|
|
|
|
From object definition above, there are two Printer objects, one for p and one for the Printer part of cp. This is usually unwanted behavior.
Often we are interested only in allowing objects to exist on their own, and limit the number of those kinds of instantiations. To satisfy such restrictions, we should declare the class constructors private, and (in the absence of friend declarations) classes with private constructors can’t be used as base classes, nor can they be embedded inside other objects.
Pseudo-constructor
In fact, private constructors are a general solution for preventing derivation. Instead of returning a reference to a single object (like what thePrinter does), we can declare a pseudo-constructor returning a pointer to a unique object to allow multiple objects.
That is, we combine the object-counting with pseudo-consturctors:
|
|
|
|
An object-counting base class
We can split the instance counting ability apart from the Printer class to reuse the limited-number-of-instance functionality.
|
|
Now modify the Printer class to use the Counted template:
|
|
Note that:
-
We use
privateinheritance here because the implementation detials of keeping track of the number of instantiated objects are nobody’s business but the author ofPrinter’s. If we use the alternative public inheritance design, then we have to give theCountedclass a virtual destructor - that will almost certainly affect size and layout of objects of classes inheriting fromCounted, as MECpp item 24 states. -
Clients may still want to know how many
Printerobjects exists, butobjectCountbecomesprivatedue to the private inheritance. To restore the public accessibility, we employ ausingdeclaration. -
After inheritance,
Printercan forget about counting objects, so thePrinterconstructor now looks like this:1 2 3 4Printer::Printer() { proceed with normal object construction; }The benifits:
- No checking of the number of objects to see if the limit is about to be exceeded
- No incrementing the number of objects in existence once the constructor is done
- Base class will always be invoked first, so if too many objects are created, a
Connted<Printer>constructor throws an exception, and thePrinterconstructor won’t even be invoked
-
Clients of the
Printerclass are required to initializemaxObjects, or there will be an error during linking for undefinedmaxObjects:1const size_t Counted<Printer>::maxObjects = 10;