Sunday, March 15, 2009

Implementing a non-copyable class in C++

Problem at hands

============

In the simplest of scenario, we basically want to prevent something like this:

class A

{

public:

A(){}

~A(){}

};

void foo(A a)
{
}

int main(int argc,char *argv[])

{

A a1;

A a2(a1); // directly invoking the copy constructor, should be disallowed

a2 = a1; // creating copies via assignment operator, should be disallowed

foo(a1); // and passing copies as parameters to other functions, should be disallowed


return 0;

}

Sol 1:Make the copy constructor and assignment operator private
==============================================
This is probably the first solution which will strike our minds.And apparantely, this would get the job done as well. Lets see it in action:
class A
{
private: // copy constructor and assignment operator
A(const A&){}
A& operator=(const A&) {}
public:

A(){}
~A(){}

};

void foo(A a)
{
}

int main(int argc,char *argv[])
{
A a1;
A a2(a1); // error! Copy constructor is private

A a3;
a3 = a1; // error! assignment operator is private

foo(a1); // error! copies cannot be created since the copy constructor is private

return 0;
}

All is good uptill now, but what about the following scenario?

#include <iostream>
using namespace std;

class A
{
private: // copy constructor and assignment operator
A(const A&)
{
cout<<"Copy constructor"<<endl;
}
A& operator=(const A&)
{
cout<<"Assignment operator"<<endl;
return *this;
}
public:
A(){}
~A(){}

void violate1()
{
A tmp(*this); // copy constructor will get invoked
}

void violate2()
{
A tmp;
tmp = *this; // assignment operator will get invoked
}
};

int main(int argc,char *argv[])
{
A a1;
a1.violate1(); // gets invoked successfully
a1.violate2(); // gets invoked successfully

return 0;
}



Since we only made the copy constructor and assignment operator private, we were able to invoke them from within member functions like violate1() and violate2. Similarly we should be able to invoke foo() from within a member function of class A. This leads us to Sol2

Sol2: Do not implement the copy constructor and assignment operator
==================================================

This is a smart trick to make the class uncopyable. We would just declare the functions and NOT implement them. Declaring them would prevent the compiler from generating its own generous versions of these functions and not implementing them would ensure that a linking error is generated if they are used anywhere in the program. Here is the implementation of this approach:

#include <iostream>
using namespace std;

class A
{
private:
A(const A&); // copy constructor declaration only
A& operator=(const A&); // assignment operator declaration only
public:
A(){}
~A(){}

void violate1()
{
A tmp(*this); // copy constructor will get invoked
}

void violate2()
{
A tmp;
tmp = *this; // assignment operator will get invoked
}
};

int main(int argc,char *argv[])
{
A a1;
a1.violate1(); // would get compiled, however a linker error would be generated
a1.violate2(); // would get compiled, however a linker error would be generated

return 0;
}

When I tried to compile it (using g++), I got the following linker errors
/tmp/ccUfNEtK.o: In function `A::violate1()':
cpp_code.cpp:(.text._ZN1A8violate1Ev[A::violate1()]+0x14): undefined reference to `A::A(A const&)'
/tmp/ccUfNEtK.o: In function `A::violate2()':
cpp_code.cpp:(.text._ZN1A8violate2Ev[A::violate2()]+0x20): undefined reference to `A::operator=(A const&)'
collect2: ld returned 1 exit status
We have achieved our goal! Copies of class A's object cannot be created now. But there is still room for improvement; It would definitely be more helpful if we can generate compile-time errors instead of link time errors. Sol3 will do just that

Sol3: Use inheritance
================

Create a class (lets call it uncopyable) with un-implemented but declared private copy
constructor and assignment operator. Inherit from this class, in order to make the derived class
uncopyable.

#include <iostream>
using namespace std;

class Uncopyable
{
private:
Uncopyable(const Uncopyable&); // copy constructor declaration only
Uncopyable& operator=(const Uncopyable&); // assignment operator declaration only
public:
Uncopyable(){};
~Uncopyable(){};
};

class A:public Uncopyable
{
public:
A(){}
~A(){}

void violate1()
{
A tmp(*this); // error! This cannot be invoked, since base class's copy constructor is private
}

void violate2()
{
A tmp;
tmp = *this; // error! assignment operator cannot be invoked, since base class has defined it as private
}
};

int main(int argc,char *argv[])
{
A a1;

A a2(a1); // error. Copy constructor cannot be invoked

a1.violate1(); // error (See above)
a2.viloate2(); // error (See above)


return 0;
}

To sum it up, uncopyable prevents copying of its derived classes from all possible scenarios:

  • Outside the class: Since its copy constructor and assignment operator is private
  • Inside member functions of the derived class: because of private copy constructor and assignment operator, the derived classes do not have access to them. And we know that the default copy constructor invokes the copy constructor of its base class. Moreover the default assignment operator also invokes the assignment operator of its base class. Since both are private, hence a compile time error would be generated if such an attempt made.
  • When passed as parameters

ACKNOWLEDGEMENTS

This approach is being taken from Scott Meyers "Effective C++".

No comments: