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
private
inheritance 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 theCounted
class 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
Printer
objects exists, butobjectCount
becomesprivate
due to the private inheritance. To restore the public accessibility, we employ ausing
declaration. -
After inheritance,
Printer
can forget about counting objects, so thePrinter
constructor now looks like this:1 2 3 4
Printer::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 thePrinter
constructor won’t even be invoked
-
Clients of the
Printer
class are required to initializemaxObjects
, or there will be an error during linking for undefinedmaxObjects
:1
const size_t Counted<Printer>::maxObjects = 10;