Skip to content

Modern C++ • Default and deleted functions

C++ provides default implementations for several functions and operators that are implicitly defined by the compiler if there are no user-defined ones. Specifically, the following class member functions are implicitly defined

1. Default Constructor
2. Copy Constructor
3. Copy assignment operator =
4. Destructor

In addition, C++11 added the following member functions

5. Move Constructor
6. Move assignment operator =

These are often called special member functions and provide basic management for object-oriented programming or for POD-like behavior (that is to use objects like C-style structs without writing additional code). Other built-in operators also have a default implementation that can be used by the programmer without being overridden, such as operator-> operator. operator->* operator.* operator:: and others.

The mechanics of these special member functions, however, have a few caveats. Most importantly,
if there is a user-declared constructor the compiler does not generate a default constructor, forcing the programmer to define one even if it does nothing, and there is no mean to indicate otherwise;
the implicitly-defined destructor is non-virtual, which requires the programmer to declare and define a virtual one for polymorphic classes;
the non-trivial user-defined functions the programmer is forced to provide are often not as efficient as the ones implicitly defined by the compiler, which are usually optimized, and many times programmers do implement functions with just a default behavior (e.g. constructor, copy);

// Old C++
 
class A {
 public:
     A(const A&){/*...*/}   // suppresses implicit default constructor
     A(){}                  // a non-trivial one must be provided to use A
}
 
class B {
     int x;
 public:
     B(int x=0)             // suppresses implicit default constructor
     : x(x) {/*...*/}       // B() is a non-trivial default behaviour
}
 
class C {
 public:
      virtual ~C() {}       // a non-trivial virtual destructor must be provided
                            // to use C polymorphically
};
 
class D : public C {
 public:
};

Since C++11 there is an elegant and more concise solution to these problems.

The default definition

To resolve the above issues we can use the default definition to force default (compiler-generated) implementation of functions when they’re suppressed by other circumstances and only the default behavior is needed.

// Modern C++
 
class A {
 public:
     A(const A&){/*...*/}      // suppresses implicit default constructor
     A() = default;            // force the default implementation
}
 
class B {
     int x = 0;
 public:
     B(int x)                 // suppresses implicit default constructor
     : x(x) {/*...*/}         // B(x) is non-trivial
     B() = default;           // but B() is not
}
 
class C {
 public:
      virtual ~C() = default;   // declare the virtual destructor and
                                // reinstate the default implementation
};
 
class D : public C {
 public:
};

Default definitions can also be declared outside of the class body, as in the following example

class Z {
 public:
    Z();
    Z(const Z&) {/*...*/}
};

inline Z::Z() = default;

Note that the inline specifier is needed to make the function non-trivial, thus more efficient.

The delete definition

Two common concerns in programming regard the prohibition of copies of objects and calls to specific methods that may result in undefined/unwanted behavior. These concerns are typically addressed using the following techniques

1. Noncopyable idiom
2. Function “hiding” (*)

(*) Not to be confused with name hiding in class derivation

The standard approaches to implement these techniques involve declaring a non-public copy constructor and assignment operator to avoid copies (or deriving from a non-copyable object, such as boost::noncopyable), and declaring methods that shouldn’t be called as private.

//Old C++
 
// Non-copyable
class A {
    A(const A&);
    A& operator=(const A&);
 public:
    A() {};
};
 
A a, a2;
A a3 = a;      // ERROR: A::A(const A&) is inaccessible
a = a2;        // ERROR: A& A::operator=(const A&) is inaccessible
 
// Classes with "hidden" methods
 
class B {
    void foo(float forbidden);   // prevent potential narrowing
 public:
    void foo(int allowed) {}
};
 
B b;
b.foo(1);
b.foo(1.5);    // ERROR: B::foo(float) is inaccessible
 
class E {
    void* operator new(size_t sz) {};   // prevent heap-allocation
 public:
};
 
E *e = new E;  // ERROR: void* E::operator new is inaccessible

There are a few problems with these approaches: first of all, making objects non-copyable by declaring a non-public copy constructor (albeit not defined) will suppress the implicit default constructor and force the programmer to provide a non-trivial one, which will not be as efficient as the one provided by the compiler, aside from turning the class into a non-POD; the readability of the code is not the best, as it does not immediately convey the intent if one does not know the details behind this mechanism.

By using the delete definition it is possible to disable default language features (default implementations) and unwanted (potentially dangerous) functions, such as those that may cause narrowing, using an elegant and concise syntax.

//Modern C++
 
// Non-copyable
class A {
public:
    A(const A&) = delete;
    A& operator=(const A&) = delete;
    A() = default;
};
 
A a, a2;
A a3 = a;      // ERROR: A::A(const A&) is a deleted function
a = a2;        // ERROR: A& A::operator=(const A&) is a deleted function
 
// Classes with "hidden" methods
 
class B {
public:
    void foo(float forbidden) = delete;
    void foo(int allowed) {}
};
 
B b;
b.foo(1);
b.foo(1.5);    // ERROR: B::foo(float) is a deleted function
 
class E {
public:
    void* operator new(size_t sz) = delete;
};
 
E *e = new E;  // ERROR: void* E::operator new is a deleted function

Note that the delete definition does not completely remove the symbol of a function but only its definition. In fact, the symbol is still needed for overload resolution and to signal any reference to these functions as an ill-formed declaration by issuing a compiler error. Deleted functions can also be used with templates to filter out the unwanted calls in a more compact syntax, like in the following example

template<typename T>
void f(T par) = delete;
void f(int par) {}
 
f(1.5);    // ERROR: f(double) is a deleted function
f('A');    // ERROR: f(char) is a deleted function
f(1);      // OK

The delete definition makes the intents immediately evident and the code more readable. In class A above, the implicit default constructor is still suppressed but can be reinstated by means of the default definition, which is recommended over the empty user-defined one as it is more efficient. Furthermore, reinstating the implicit default constructor allows the class to be used as a POD. In class B, there is no need to make unwanted function calls private as, even if they’re in the public interface, calling them when they’re marked as “deleted” will cause a compilation error.

The general rule here is to always prefer the more efficient X()=default over empty X(){} whenever the circumstances require the declaration of a special member function X() and the default behavior is all that’s needed, or do not declare X() at all if the circumstances do not require you to do so and let the compiler generate its implementation. And for functions that should not be called, always define them with delete instead of hiding them using old-style trickery.

Published inModern C++