Uses of Classes with only Private and Protected Constructors

Uses of Classes with only Private and Protected Constructors

By Francis Glassborow

Overload, 1(3):, August 1993


A derivation of a paper originally presented to the European C++ Conference July 8th-9th 1993 at the Hilton Hotel, Munich.

The main aim of this paper is to present some ideas of ways in which the use of the access control keywords applied to constructors (and destructors) can be used to add constraints to the creation of objects. The use of the 'friend' keyword is a valuable asset in a number of idioms.

I make no claims that any of the following is original but in many cases case I worked out the methods for myself and for most others I am indebted to comments and contributions made be other delegates to the Munich Conference. Some I have since come across in the writings of others. Finally I am indebted to Robert Murray (a regular contributor to C++ Report) for the idea of inhibiting the use of a class as a base class.

I also make no claims that the methods I describe are either better than more commonly known techniques or are always sound examples of good programming. However good programming is made better when the programmer is aware of the wide range of techniques available and can exercise good judgement to select the most appropriate for a specific problem.

Extensions to the language are another factor that should be taken into account . Some proposed extensions add significantly to the language, others merely make already available techniques more readable and accessible to the average programmer. The former need to be examined with great care because C++ is already a large language based on sometimes tenuous historical foundations. The later, I believe, should be encouraged as they make the already achievable, easier.

Many inexperienced programmers view the keywords 'private' and 'protected' in C++ as access constraints on the member functions and data of an object belonging to a class. However C++, via one of the applications of 'static', supports functions and data that are members of the class rather than any individual object. Access control is also useful in that context.

Constructors (and, to a lesser extent, destructors) though not declared static are much more class functions than object functions. There primary job is to manage the creation (and destruction) of objects.

The access keywords are applicable to class members as well as to object members. As such 'private' (and its companion, 'protected') can be used to apply constraints on the use of class members. The constraints that are applied to constructors can be classified as follows:

1) Limit availability of class object to a particular scope, either the class scope or another function or class scope granted access via 'friend'.

2) Prevent the declaration of global or auto variables of the class.

3) Inhibit copy construction of class objects.

4) Inhibit the creation of any objects of the class

5) Inhibit the use of the class as a base class

To some extent the familiarity of a programmer with each of these constraints on constructors is a measure of their familiarity with the potential of C++.

The novice finds any private constructor an eccentricity and is initially mystified by it. In fact many seem to assume that 'private' will not have an effect on a constructor. Their compiler quickly corrects this mis-understanding.

Fairly early in the process of learning C++ a programmer will come across the third item above. Good texts and well thought out training courses include an item on inhibiting copy construction when the default copy constructor will not do what is wanted and the programmer is not yet ready to provide one themselves.

Actually this constraint on a copy constructor is pretty severe and greatly limits the ways in which objects of the class can be used. It would be a rare program that could manage with a private copy constructor for a class with objects that include data items rather than only member functions. The latter type of class probably shouldn't be instantiated: how would you distinguish one instantiation from another?

Though this inhibition of copy construction is probably the earliest example of a private constructor that a programmer will meet, it is also probably the least useful when used in isolation. There would seem to be little more to be said about this type of private constructor.

I have not come across any useful instances of a fully defined private copy constructor in classes with one or more public constructors. If you come across any such instances I would be delighted to add them to my menagerie of weird classes.

Local and Nested classes

Some authors make a great play of C++'s support of local and nested classes. This is really odd because such facilities have existed almost since the start of C++. Indeed, as we shall see, the current specification of these in C++ actually applies constraints that the following method does not.

If you want to restrict a class by constraining access to it to a second class (nested class) or to a specific function (local class) most of the appropriate functionality can be provided by the following framework:

class T {
friend @@@@@ /* replace @@@@@ with the required class or function name */
T(...){ // constructor definition
}

T(const T & t); // private copy constructor declared
// the rest of the class declaration
}

Note that this class cannot be used as a base class. ON the other hand if you change the access to the constructor to protected you have fatally damaged the constraint.

It is only fair to ask what extra facilities are provided by true local and nested classes. Basically two extras are provided by these. First is the avoidance of namespace pollution. Second you regain the ability to use 'private' to constrain access to class T members from the enclosing scope. The first of these is quite important in large projects where the single global namespace is all too easily polluted. This will become less important when 'namespace' is introduced into C++.

I am not convinced that the second item (constraining access) offers any great advantage in the context of a local or nested class. On the other hand the above technique allows a single specialist class to be a 'local' or 'nested' member of several functions and/or classes.

As there are some other constraints applied to local classes as currently provided in the standards Working Paper. These include prohibiting static members from local classes and requiring the entire definition to be within the class declaration. In the case of nested classes there are restrictions on the type names available. In view of these constraints on the normal forms of local and nested classes the above method of providing restricted access to a class may remain as a viable technique even when all compilers support the normal forms.

One objection I have heard raised by traditionalists to both this instance and a number of the following is that it produces a tight binding between two separate program objects. I think that this is symptomatic of an entirely false view of C++ and Abstract Data Types and objects.

Abstract Data Types are not effectively encapsulated in a single class. Generally an ADT is best represented by a tightly bound cluster of classes and global functions. These form a de facto module which could be encapsulated in a pair of source code files; a header file providing the public interface and a file of code that implements the interface. I would suggest that even here we may have to conceptually separate parts of the interface information that enables the system to link to the implementation from those parts that are intended for use by the rest of the program world.

In general it is not sensible to implement an ADT without the use of at least some friend functions. Those familiar with methods for managing libraries designed to support more than one platform or machine architecture will know that two or more closely coupled classes are often the most effective way of providing a consistent interface with alternative (system dependant) implementations.

Those with even more experience may be familiar with techniques for supporting multiple interfaces to a single complex but well defined data structure.

The above few paragraphs have little to do with the direct subject of this paper (and have only been inserted in this derived version) but experience shows that it is an area where many programmers need to change their interface to C++.

Nested Functions

I hear programmers refer to nested function extensions provided by some compilers (I believe GNU G++ is one such) with either awe or horror. Many seem to think that such an extension is dangerous and should not be encouraged. I find this attitude strange because all the functionality of nested functions has been implicit in C++ since 'friend' was implemented.

Consider:

class T {
friend X();
T();
T(const T & t);
static void fn(){cout<<"nested function"<<endl;}
};

This effectively provides T::fn() nested inside X(). Its nice to have nested functions provided explicitly but this should demonstrate that making it explicit is all that need be happening. As we can nest functions when we want to, perhaps those involved with developing the standard should add a suitable standard syntax for nested functions immediately and thereby limit namespace pollution.

Note that one of the major purposes of nesting functions is to increase the binding between two functions while ensuring that the inner function cannot be used externally. One problem is that of considering overload resolution. The method I have outlined ensures that such an issue does not arise as the encapsulating class name will specifically identify the scope where the function is located. I leave it to the reader to explore the consequences of calling a member function that does not exist. If you do not know the answer without investigation then you still have much work to do if you expect your programs to work the way you intend.

Shared Data

The first programming language I learnt was FORTRAN. It provided a facility by which data could be declared COMMON to a number of modules of a program. In those days it was an important facility for allowing clear communication between overlays. Overlays were far more important in the early days where memory was measured in kilobytes and having more than 16K was a luxury. None-the-less the idea of providing a common data block shared between specified parts of a program that had little other communication still has its uses.

The following idiom may be useful in such circumstances.

In the ancestral language, C, identifiers were either local to a block, local to a file or global. The arguments about global data will continue to rage but most programmers want easy methods for restricted sharing of data between functions. The classic student avoidance of globals (put all your global variables in a struct in main and pass a pointer to it to all your functions) entirely misses the real danger lurking in global variables.

C++ provides a number of other ways of restricting access to data. One that deserves to be better known is exemplified by:

class T {
friend ... // list all functions sharing data
T();
T(const T &);  // prevent use of inheritance and composition
static ...    // list of variables common to the functions
}

This method has several advantages. For me, the friend list is one of them. It provides specific and necessary documentation of all the functions (and, possibly, classes) that have direct access to this data.

Another way of achieving this is to leave out the friend statements and put the function prototypes in the class as static member functions. The disadvantage of this is that all uses of such functions have to be scoped to the class. The method above only requires the data to be scoped within the friend functions and is transparent to the rest of your code.

Preventing global and auto objects

Programmers who have only worked with single tasking operating systems often look mystified when I talk about the need to prevent construction of global or auto objects. Those with more experience and those who have considered the problems of writing for products such as MS Windows or X are aware of the problem but do not always know that a solution exists.

I first became conscious of the problem when my phone rang one day and I was asked "Is 'delete this' legal." I had never considered the problem before. On checking I found that libraries accompanying both Borland C++ and Microsoft Visual C++ contained 'delete this'.

Clearly the statement is syntactically well formed so the question came down to whether there was any reason to specifically ban that construct. Put it another way, could a ban be enforced and if it was, would that make a legitimate piece of source code illegal? The answers are 'no' and 'yes'.

'delete this' is sometimes referred to as the 'suicide statement' (I recently had a programmer talk about a Caligula function, one that destroyed its siblings, parents and children. That is another issue and I wonder if there is any legitimate use for such a function.) because the object instigates its own destruction. 'delete this' is a statement that should be used with great care because it has the potential for seriously damaging your product. Auto and global objects cannot commit suicide successfully because their 'ghosts' will hang around to haunt you.

Consider:

class T {
char * c; public:
T(char* x) : c(new char[strlen(x)+1]) {strcpy(c,x);}
~T(){delete[]  c;}
void suicide() {delete this}
};

int main()
{
T ex("disaster");
ex.suicide();
return 0;
}

Now the destructor for 'ex' is called twice, once via suicide() and once when 'ex' goes out of scope. You will probably get away with it in such a simple case but you certainly will not in most situations where the statement is used to handle a real problem.

This failure to properly destroy the object with 'delete this' is a fundamental problem. It is impossible to stop the call of a destructor for a variable that is going out of scope.

What we can do is to prevent auto, static local or global objects capable of suicide from ever coming into existence.

The way to achieve this is to make all constructors for the class private. You must be careful to remember that there are two constructors provided by the compiler. Both must be constrained by making them private or protected. The choice between these two is governed by whether you want your class to be available as a base class or not. Make the constructors private and your class cannot be a base class because the derived class will not be able to construct the base object (unless you grant the derived class friend status but see later on for a development of this idea).

If you choose to make one of these classes available as a base class it is essential that the suicide position is fully documented. If you do not, a client's derived class may destroy your protective mechanisms with the resulting risk of an auto, static local or global object suiciding. This is because any public constructors for derived classes will have access to the protected constructors of the base class. Auto, static local and global objects of the derived type can then be constructed. Any call to the suicide function then has all the potential for a real disaster. The 'delete this' in the original suicide function will refer to the base class object, not the derived object.

Leaving these problems aside, there are several mechanisms for using private constructors to create objects. The fundamental principle is to use a pointer to handle an object that is created with new. Do not be tempted to even consider references in this context, to do so will almost certainly undo all that you had achieved.

The naive programmer does something like this:

class T {
friend T* make_T();
int i[20];
T(){};
T(const T & t) // inhibit copying
~T(){};
public:
// public interface
}

T * make_T() { return new T();}

The problem with this is that we are (unnecessarily) trusting the programmer to capture the returned pointer. We should ensure that there is a pointer by passing it as a parameter so make_T() should be:

boolean make_T(T * t){ 
t=new T;
if (t) return TRUE;
else return FALSE;
}

The reason that the destructor has been made private (protected) is that in the context of suicide classes it is almost always essential that the object IS destroyed by suicide and not by some other mechanism.

To make this clear consider the common use of suicide classes to handle window objects in an event driven environment such as MS Windows.

The problem is that there are two objects involved, the program object created as a member of an appropriate class and the Windows object. When the time comes to destroy a window both objects must be destroyed by the same event.

If you first destroy the program object there is nothing left to send a message to Windows. On the other hand if you first send a message to Windows there is the danger that the programmer forgets to destroy the program object.

The safe way to do this is to cause the same function to send a message to Windows and suicide. The Windows experts can probably elaborate with more sophisticated mechanisms via callback functions.

Mustn't be a Base

I am not at all certain that this mechanism has any real life practical uses and I am indebted to Rob Murray for drawing my attention to the idea. I would welcome examples of its practical use.

You design a class 'end_of_branch' that must not ever become a base class. How do you ensure that no programmer manages to use it as a base? The method is as follows (I have slightly amended Rob's solution so that it will actually work despite the potential for using pathological behaviour of copy constructors to self copy an object):

class inhibit 
{
friend class end_of_branch;
// and any other similar classes that you wish to inhibit
inhibit(){};
inhibit( const inhibit & i){};
};

class end_of_branch : virtual inhibit
{
// the code for your class
}

Now if you try to use end_of_branch as a base class for a derived class D, for example:

class D : end_of_branch
{
};

Then the compiler will reject it because the rules for construction require the constructors for virtual bases to be called directly and not via the inheritance hierarchy. Actually it is not quite true as the following shows:

class DD : end_of_branch 
{
end_of_branch();
end_of_branch(const end_of_branch & e);
// remaining code
};

By declaring the constructors but not defining them you prevent the compiler from detecting the problem and as long as you never call either constructor there will not be a link time error. The result is that you can have a derived class but you cannot have derived objects. A number of items in this paper show that such a class can have its uses.

In his original presentation, Rob forgot the copy constructor with the result that a derived class could be sneaked past the compiler by declaring an ordinary constructor but not defining it and then using the copy constructor to construct objects.

This is all great fun and works on paper but before you actually inhibit a class by this mechanism go away and do some research on the cost of having a virtual base class. The most obvious candidates for the method are what Bjarne Stroustrup calls concrete data types (things like complex, string etc.) which the programmer wants to make as like a built-in as possible. With the current methods of supporting virtual base classes there is no way that a sane programmer would want to use this method to protect a concrete data type.

And the final problem is that there is nothing you can do against the determined programmer who insists on wanting to grab your class as a base class. Consider the following:

class wrapper 
{
public:
end_of_branch e;
};

This  new class  has almost  exactly the functionality of end_of_branch without the inhibition. Using classes derived from wrapper will be a little more awkward and some of the overloading and hiding rules will be changed but that is about all.

If grey haired writers can come up with schemes like this what will the young whiz-kids produce? If you work for a company that does not use code inspections then I suggest that either they change their policy or you find another employer. The job of trying to maintain code that is the result of the creative programming from an under-supervised programmer is a nightmare.

Providing restricted mutual friendship

This is not my idea but one that was passed on to me at Munich. The problem is basically to provide mutual access between a pair of functions in two different classes so that each function has access to the other one but no more. All you can do by straightforward methods is to allow each function access to the whole of the other class. This is fine normally but if you want to ensure that the compiler will resist any access other than that specifically required how can you do it?

Try this:

class X 
{
friend LINK::xfn();
fn();
// rest of code
};

class Y
{
friend LINK::yfn();
fn();
// rest of code
};
class LINK 
{
static xfn(){X::fn();}
static yfn(){Y::fn();}
friend X::fn();
friend Y::fn();
};

Remember that friendship is not transitive so each of the two original functions can only get at the wrapped version of the other in LINK. Of course if you are being that careful to allow the compiler to detect problems you do not want any LINK objects instantiated so you better inhibit construction as well.

class-as-object

Writing a class with both default constructors declared as private and not defined would seem to be about as useless a piece of source as you could imagine. But we have already seen several uses of such classes. In those, however, the constraint was desirable, not necessary. In this final example it is essential that the class cannot be instantiated.

Oddly, among all the types of class with private constructors this is the first type that I actually had a use for.

One of the principles of object-oriented programming is to create program objects to represent real world objects. The question arises as how to represent an essentially unique object?

I first came across this when I was sitting in on a test run of a course for C programmers converting to C++. One of the course exercises was to develop a Screen class. The specification was that the constructor should switch the VDU to graphics mode.

The destructor would revert the screen back to text mode.

The purpose of the exercise was to demonstrate that a class object did not need data members. A secondary purpose was that students would have to anticipate problems created by the instantiation of a second object before the destruction of the first. The course designer suggested that this should be done via a static data member that the constructor could inspect. If an object already existed the constructor would abort the program. Even with exception handling this approach seems flawed.

What is needed is an object to keep track of the current display mode. Even better it should maintain a record of prior states on a stack (or other appropriate data structure). The constructor/destructor approach does not meet the needs of the problem.

One solution would be to create a screen object at the start of the program and destruct it at the end. I am unhappy with this approach because, in the context, there is only one screen and it should not be possible to create more than one program object to interact with a unique real world object. I am also profoundly unhappy with simply generating an abort if an attempt is made to instantiate a second object. The constructor method does not leave much space for a graceful exit.

In the context of a course this kind of exercise is useful because it brings programmers face to face with some of the problems associated with constructors. At the time I felt that a better solution to representing unique objects must exist. I must admit that it was several months before I recognised a potential solution; treat the class-as-object.

Consider:

class Screen 
{
static stack_of_int s;
Screen();
Screen(const Screen & ss);
public:
// static functions to handle desired functionality
}

As the constructor is private (and undefined) it is impossible to construct a Screen object. Therefore it may seem unnecessary to include a copy constructor but this is not the case. Bizarrely, C++ allows you to construct an object with a copy of itself. For example:

Screen s(s);

Would create a Screen object, s, unless the copy constructor has been inhibited as well as the ordinary default constructor. Alternatively you can declare a private destructor as all the compilers I have tried recognise this and prevent any objects being constructed. This would be sufficient to prevent compilation but it is a quality of implementation issue for the compiler to recognise the problem at construction time. Anyway take your choice but watch out for that unexpected use of a copy constructor.

The key idea that has to be coupled with private constructors when implementing a class-as-object is the use of static to make all members class-members rather than object-members.

We can now add whatever other facilities and functionality that we want for our Screen object in the secure knowledge that there is a single program class-as-object connected to our single real world screen.

This idea is fine as far as it goes but how do we handle objects that are not unique but must be uniquely addressed. A good example in the PC world is that of managing interfacing to serial ports. A design that allowed two objects to try to use a serial port simultaneously is almost certainly faulty. But to try to use object-oriented methods and not provide an object to manage a serial port seems perverse.

There are certainly a number of alternative approaches including having a unique monolithic serial port class. Another approach is to have the serial port class maintain a static list of ports that are currently assigned.

The problem with the latter approach is that it suffers from similar problems to the putative Screen class, what does the constructor do when it finds that the required port is already in use?

I think this is the wrong direction. Though there are often several serial ports each one should be uniquely addressable and therefore have a single program object associated with it. Once I phrased the problem in these terms and started thinking of implementing an individual class-as-object for each port an answer surfaced, templates. Consider:

template <int port> class SerialPort
{
SerialPort();
// static member data to handle a port
public:
// static member functions to handle a port
}

Now it is impossible to have more than one handler per serial port (well as long as you don't provide a handler via an alternative method). There are no constructors to fail but we do still have some things to consider.

We cannot pass a class-as-object to a function. We cannot have arrays of them and all such items must be specifically identified at compile time. For example if we want a program to initialise all existing serial ports and create a list of those available we cannot write code such as:

for (int i=1;i<5;i++) SerialPort<i>::init();

Instead I must unroll such a loop. As there will only be a limited number of serial ports I can live with that solution.

In fact every time we need to make run-time decisions about which serial port to use we will have to provide switch statements or something similar. In this case the problems caused by using a class-as-object may be acceptable but it would be nice to have some other method.

If you elect to implement serial port handling this way it is probably a good idea to include all the functionality in a single normal but data free class (with object instatiation inhibited) and use the template class as a wrapper so that code duplication is minimised.

I think that the use of a class-as-object idiom is only appropriate to handling real-world objects that you expect to be globally available to your program. In such circumstances we can live with the handicaps introduced because we cannot use pointers, references, nor directly select an object at run­time.

Conclusion

I hope you have enjoyed this ramble through some of the odd uses of classes with only private copy constructors. Please do not stop here, instead get out your favourite compiler and text editor and explore a bit further. When you have done so report back via these pages. The principle object of papers such as this one is to set programmers thinking and to dig out some of the more obscure idioms that some are already using.

I hope that your editor will be swamped with responses and will find space to publish them. I give him my full consent to include the vitriol from those that consider such procedures as the above have no place in a well ordered programming environment.

Finally, as the Harpist would assure you, there are bound to be errors in the above to catch the too casual reader.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.