Blocking Undesirable Object Copying and Implicit Conversions

ingletons, stream objects, and other classes must disable copying and assignment. However, C++ doesn’t yet provide a direct mechanism for disabling copying. Another related problem exists with undesirable conversions of arguments passed to a function. The following sections will show how to work around these limitations to block undesirable copying and assignments and prevent implicit conversions of arguments.

There’s no means to suppress the compiler’s automatic declaration of a copy constructor and an assignment operator—even if you wish to disable copying and assignment of certain objects. Likewise, you cannot instruct the compiler to disable automatic conversions of arguments passed to a function.

Declare the copy constructor and assignment operator explicitly as private members without defining them. To prevent undesirable implicit conversions of arguments, declare dummy signatures that intercept the wrong types of arguments.

Describing the Problem
Suppose you’re designing a class whose objects must neither be copied nor assigned to. Because the compiler declares a copy constructor and an assignment operator implicitly, clients will still be able to copy and assign such objects:

class Myfile{public: explicit Myfile(const char * name); Myfile(); virtual ~Myfile();  virtual int open(const char * name); virtual int close();};int main(){ Myfile datafile("mydata.txt"); Myfile otherfile(datafile);//undesirable copy ctor Myfile third; third=otherfile; //undesirable assignment}

The implicitly declared copy constructor and assignment operator allow the program to copy construct and assign Myfileobjects, possibly causing undefined behavior. The problem is that you have no way to instruct the compiler to not declare these member functions implicitly.

Undesirable implicit conversions present a similar challenge for programmers. Take for example the standard class template std::complex. Its non-default constructor takes one or two floating point arguments:

std::complex  compnum2(NULL);//also compiles!

This is well and good. However, because C++ is lenient with respect to conversions of arithmetic types, you can easily get into trouble when the wrong type of argument is passed to the constructor:

std::complex  compnum=true;//compiles!std::complex  compnum2=(NULL);//also compiles!

Not only does this weird code compile successfully, you won’t even get a warning from your compiler. The problem is that the compiler implicitly converts arguments such as true and NULL to double implicitly. That doubleis then used as the initializer of a complex object. Such conversions most likely indicate a bug but they compile silently. The solution for both problems is based on the same technique. Let’s take a look at it.

Blocking Copying and Assignment
A previous 10-Minute Solutiondemonstrated a technique for blocking object copying—but it has a flaw which calls for revision.

As said earlier, there’s no way to instruct the compiler to not declare a copy constructor and an assignment operator implicitly as public members of the class—unless youdeclare them explicitly. If you declare these members explicitly as private members, they will become inaccessible and, hence, unusable:

class Myfile{private: Myfile(const Myfile&); //block copying Myfile& operator=(const Myfile&); //block assignmentpublic: explicit Myfile(const char * name); Myfile(); virtual ~Myfile(); virtual int open(const char * name); virtual int close();};

Notice how the private copy constructor and assignment operators block copying and assignment:

int main(){ Myfile datafile("mydata.txt");//OK Myfile file2(datafile);//error, copy ctor inaccessible Myfile third; third=file2; //error, assignment op inaccessible}

This technique differs in one crucial aspect from the technique shown in the previous 10-Minute solution. In the code above, the private member functions are only declared; they are not implemented. If you implement the private copy constructor and the assignment operators, any member function of Myfilewill be able to invoke them. Using the new technique, the program will fail to link should a member function invoke the copy constructor or assignment operator.

Blocking Undesirable Conversion
In 2006, I discussed a private case of disabling undesirable conversions from bool. While the “indirect conversion” idiom works satisfactorily with Boolean types, it doesn’t solve the general problem of implicit conversions among arithmetic types. Consider:

void func(long double){/**/}int main(){ long double n=0; int x=0; func(n);//OK, perfect match of argument and parameter func(x);//OK, implicit int to long double conversion}

The second func() call compiles successfully, although it may well be buggy. In C++03, there’s no simple way to exclude integral types from being silently promoted to floating-point types when an argument doesn’t match perfectly the parameter specified in the signature. If int to long double conversion is undesirable, you can prevent it by declaring additional overloads of func()that will intercept arguments of the wrong types. Notice that these “interceptors” should only be declared, not implemented:

void func(long double) {}//list of "interceptors"void func(int);void func(bool);void func(short);void func(char);

After adding the interceptors, if the wrong type of argument is passed to func(), you’ll get a linkage error:

int main(){ long double n=0; int x=0; func(n);//OK, perfect match of argument and parameter func(x);//linkage error: void func(int) undefined}

This technique has one limitation: your arguments must match the parameter exactly. If you pass an argument of type float or double instead of long double, the compiler will consider the call ambiguous and issue an error message:

float n=0;func(n);//error, ambiguous call

The compiler can’t decide which overloaded version of func()to call because there’s no perfect match between the argument’s type and any of the parameters of the overload set. Therefore, this technique is mostly useful when a perfect match between arguments and parameters are required.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin


Recent Articles: