11.1 ACCESS CONTROL OF CONSTRUCTORS


11.1 ACCESS CONTROL OF CONSTRUCTORS

Constructors are not always placed in the public section of a class. Sometimes it is necessary to place one or more constructors in the private or the protected section. While this forbids certain forms of object creation, it makes possible other behaviors by a class.

In this section, we will show two examples that call for limiting the access to a constructor. In the first example, our goal will be to program up a class in such a way that only a limited number of objects are allowed to be made from the class. Our second example, specific to C++, addresses situations when it is not possible to give meaningful initializations to the data members in the no-arg constructor of a class. As we will point out, it is best to place such a no-arg constructor in a nonpublic section of the class. (The issue addressed in the second example does not arise for the case of Java because a class-type data member in Java can always be default-initialized to the null object reference, as pointed out in Chapter 7.)

11.1.1 Limiting the Number of Objects

Shown below[1] is a Java class X whose sole constructor, in line (B), is private. Therefore, it is not possible to make instances of this class in the usual manner—that is, by invoking its constructor.

The class provides in line (C) a public method, makeInstanceOfX(), that returns the same unique instance of the class each time the method is invoked. As shown in line (D), this unique instance of the class is not created until the first request for an object of type X is received.[2] The class X as written only allows a single object of X to be made. With a slight modification, we could have the class allow a certain designated but fixed number of objects of type X to be made.

 
//Singleton.java class X { private int n; private static X unique; //(A) private X( int m ){ n = m; } //(B) public static X makeInstanceOfX(){ //(C) if ( unique == null ) unique = new X( 10 ); //(D) return unique; } } class Test { public static void main( String [] args ) { X xobj_1 = X.makeInstanceOfX(); X xobj_2 = X.makeInstanceOfX(); System.out.println( xobj_1 == xobj_2 ); // true } }

Shown below is a C++ version of the Java example. The constructor of this class is also in the private section in line (B); so it cannot be accessed by either a stand-alone function or other classes. Therefore, it is not possible to make instances of this class in the usual manner, that is, by invoking its constructor. The class provides in line (E) a public method, makeInstanceOfX(), that returns the same unique instance of the class each time the method is invoked.

Shown in lines (C) and (D) are the private placements of the copy constructor and the copy assignment operator of the class—notions that will be explained later in this chapter. By making them private, it becomes impossible to make a duplicate of the unique instance of X supplied by the makeInstanceOfX() method. In other words, we cannot use ploys shown in lines (H) and (I) for constructing duplicates of the unique instance of X.

The initialization in line (F) will make sense after you have gone through the material on static class members in the rest of this chapter. And the function in line (G) will make more sense after you have gone through Chapter 12. In the function defined in line (G), we overload the equality operator "==" to test whether or not two X objects are the same because they are located at the same place in the memory.

 
//Singleton.cc #include <iostream> using namespace std; class X { int n; static X* unique; //(A) X( int m ){ n = m; } //(B) X( const X& ); //(C) X& operator=( const X& ); //(D) public: static X& makeInstanceOfX() { //(E) if ( unique == 0 ) unique = new X( 10 ); return *unique; } }; X* X::unique = 0; //(F) bool operator==( const X& obj1, const X& obj2 ) { //(G) return &obj1 == &obj2 ? true : false; } int main() { X& xobj_1 = X::makeInstanceOfX(); X& xobj_2 = X::makeInstanceOfX(); cout << (xobj_1 == xobj_2) << endl; // true // X xobj_3 = xobj_1 //(H) // xobj_2 = xobj_1; //(I) return 0; }

11.1.2 Limiting the Access to No-Arg Constructor in C++

A constructor that in certain situations is a good candidate for the private or the protected section of a C++ class is the no-arg constructor. Ideally, when a new object is constructed, no data member of the constructed object should remain uninitialized. This precept applies equally well to a no-arg constructor also. To fulfill this ideal, a no-arg constructor must obviously provide default initializations for the data members of a class. But a default initialization for a class-type data member may make no sense whatsoever in certain contexts. When that happens, a no-arg constructor may have to leave one or more of the data members uninitialized. Since constructors that leave data members uninitialized can be a source of hard-to-trace bugs, it may become necessary to place such a constructor in the private or the protected section of a class.

In this section, we will consider no-arg constructors in the context of the initializations needed for C++ arrays and other container types. As mentioned earlier in Chapters 5 and 7, it is not possible in C++ to construct an array or a container of class-type objects if the type does not support a no-arg constructor.

For example, in the program shown below, it is obviously not possible to construct an array or any other container type for storing objects of the class X shown below. Since the class has a programmer-supplied constructor, it does not possess a system-supplied no-arg constructor. But, memory allocation for arrays and the container classes requires the container elements to provide either a no-arg constructor explicitly, or no constructors at all, in which case the class would possess a system-supplied default no-arg constructor[3].

 
//NoArgMissing.cc #include <vector> class X { int p; public: X( int m ) : p(m) {} }; int main() { X xob; // Error, no-arg constructor missing X arr[100]; // Error, no-arg constructor missing vector<X> vec1( 100 ); // Error, no-arg constructor missing // OK, no-arg constructor not needed: vector<X> vec2; //(A) X xobj( 12 ); vec2.push_back( xobj ); return 0; }

So, if you needed to make arrays and other container types of objects of class X, you could try to provide it explicitly with a no-arg constructor:

     X(){} 

or, more smartly,

     X():p( 0 ){} 

if the number 0 is an appropriate choice for the initialization of the data member p.

A do-nothing no-arg constructor, our first choice above, leaves the data members uninitialized, leading to possible sources of bugs for the case of large classes in complex programs. So, ideally, if you must specify a no-arg constructor you should also provide initializations for the data members, as we did in our second choice above. But, unfortunately, that is not always possible, especially when the data members of a class are other class-types whose design may not be under your control. Since a meaningless no-arg constructor in the public section of a class can be abused, a solution to the dilemma may be to place a no-arg constructor in the private section of a class and to declare a container type to be a friend of the class,[4] as we show below:

 
//PrivateConstructor.cc #include <vector> using namespace std; class X { friend class vector<X>; //(A) int p; X(){} //(B) public: X( int m ) : p(m) {} }; int main() { // X xob1; // Error, no-arg constructor private // X arr[ 100 ]; // Error, no-arg constructor private vector<X> vec(100); // OK now X xob2( 12 ); vec[0] = xob2; return 0; }

The friend declaration in line (A) makes the private data members of class X accessible to objects of type vector<X>. Thus the private no-arg constructor in line (B) will become accessible to vector<X>, but will remain inaccessible for more general construction of objects of type X.

But note that this solution—placing a constructor in the private section and declaring a container class to be a friend—can only be used for class type containers. It obviously will not work for array types since a C++ array is not a class.

While C++ containers, including arrays, can hold objects directly, Java containers only hold references to objects. So a Java container does not need access to a constructor to figure out how to allocate memory for each element in the container. So the C++'s requirement that a class possess an accessible no-arg constructor does not apply to Java.

In both C++ and Java, if a class has one or more constructors in its private section but none in the public section, then it would not be possible to construct individual objects of such a class. Possessing at least one constructor—even if it is in the private section—would prevent the system from supplying the class with a default public no-arg constructor.

[1]This section is best read after the reader has gone through the rest of this chapter and Chapter 12. This section, although important, stands on its own and is not crucial to understanding of the rest of this chapter.

[2]This is referred to as lazy instantiation.

[3]If we did not want to preallocate memory for a vector, which is the case in line (A) of the program, it is not necessary for X to possess a no-arg constructor in order to construct a vector for storing elements of type X.

[4]The C++ concept of a friend was introduced in Chapter 3.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net