Monday, 17 March 2014

C++11 features - Explicitly defaulted/deleted member functions

Problems: the default 4 generated items by compiler
class Foo{
};
An empty class has four signature generated by compiler by default:
Foo(); // constructor
Foo(Foo&); // copy constructor
Foo& operator=(const Foo&); // assignment operator
~Foo(); // destructor
(of course some other default operator are generator as well, for instance global new operator, ".", "&", "->" operator)
Quick quiz: sizeof(A) = ?
Most of time there is no problem for default constructor and desctrucotr. The problem lies with the default copy constructor and assignment operator. By default the copy constructor and assignment operator generated by compiler are doing member-wise copy/assignment. Simply there are 3 options.
1. Define your own copy constructor and assignment operator. For instance there are resources allocated in the class, like heap memory, file handler, socket connection and data sessions.
2. Accept the default copy constructor and assignment operator. Then do nothing. Simply ignore the declaration in your class.
3. Suppress the default copy constructor and assignment operator and prefer this class un-copyable or un-assignable.

Here we are concentrating the 3rd option and discuss what C++11 provides to make these option more convenient and clear.

Solutions in C++03:
1. Make this class un-copyable or un-assignable by declaring the copy constructor or assignment operator explicitly private and having no implementation.
class Foo {
public:

private:
  // exmplicitly declare them private and
  // do not provide any implementation
  Foo(&Foo);
  Foo& operator(const Foo&);
};

Declare them private: prevent any copy or assignment outside this class and stop them being called from sub-classes legitimately as well. But still one defect: can not stop friend classes and function calling them.
Provide no implementation: this is actually stopping friend classes and functions calling them. The compiler will signal linking errors when they are actually called from friends. Simply the declarations do not have function body to match.

2. (Multi)-inheritance from a base uncopyable/assignable class
class Uncopyable{
private:
  Uncopyable(Uncopyable&);
  Uncopyable& operator=(const Uncopyable&);
};

// Foo inherit from Uncopyable class
class Foo : public Uncopyable, public Base {
......
};

The idea is to take the 1st idea into a base class to be inherited by all un-copyable class. Because it sub-classes's copy constructor and assignment operator will try to call its base class counterpart, but it fails to do
so as they are declared as private and no implementation provided.

Solution in C++11:
1. "delete"
An new pair was introduced as default/delete. The two solution provided as in C++03 will look like:
class Foo {
  Foo(Foo&) = delete;
  Foo& operator=(Foo&) = delete;
};

class Uncopyable {
  Uncopyable(Uncopyable&) = delete;
  Uncopyable& operator=(const Uncopyable&) = delete;
};

This is crystal clear about we would like to do here - suppress the default copy assignment and assignment operator and make this class un-copyable. No ambiguity to sub-class and friends.

2. "default"
C++11 also provide "default" keyword to tell the compiler that the programmer would like to use the default version generated by compilers. This means that the programmers are happy with the member-wise copy
of copy constructor and assignment operator. If an explicit version is preferred, simple declare it and provide the implementation.

3. Extra usage of "delete"
It can be used to suppress type conversion/promotion when finding closest function signature (function overloading) or in template specialization (no matter partial or full) and template overloading.
void foo(double) {}
void foo(int) = delete; // no type promotion from int to double
This will suppress calling like foo(1). Compilers will generate error.

template<typename T> void foo(T) {}
template<> void foo<int> (int)  = delete;
This will suppress this template function to be instantiate to take a int type.

I have not seen this type of things are useful yet, because most of occasions the program will require special implementation against a certain type of argument or a series of arguments. For instance to have different implementation with void foo(int) {} and special implementation with template<> void foo<Complex> (Complex) by template specification/overloading.

Especially for template class, there is a single template function can be used for all types. So impossible to enumerate all by "delete". All need to keep in mind is that what scope it is designed for.

template<typename T>
T Foo(T a, T b) {return a + b;}

template<>
Complex Foo<Complex>(Complex a, Complex b) {
return Complex(a.x+b.x, a.y+b.y)
}

So really "delete" is not the solution. This usage is really useless and C+11 should not really wasting time on facilitating this sort of convenience.




No comments:

Post a Comment