The new syntax "= default" in C++11

Learn the new syntax "= default" in c++11 with practical examples, diagrams, and best practices. Covers c++, c++11 development techniques with visual explanations.

Mastering C++11's = default and = delete for Special Member Functions

Hero image for The new syntax "= default" in C++11

Explore the C++11 features = default and = delete to explicitly control the generation of special member functions, enhancing code clarity, safety, and performance.

Before C++11, the compiler would implicitly declare and define certain special member functions (default constructor, copy constructor, copy assignment operator, destructor) if you didn't declare them yourself. While convenient, this implicit behavior could sometimes lead to subtle bugs or prevent optimizations. C++11 introduced the = default and = delete specifiers, giving developers explicit control over these functions. This article delves into how these specifiers work, their benefits, and practical use cases.

Understanding Special Member Functions

Special member functions are a set of functions that the C++ compiler can automatically generate for a class. They are crucial for managing the lifetime and value semantics of objects. Prior to C++11, if you declared any constructor, the compiler would no longer generate a default constructor. Similarly, if you declared a copy constructor or copy assignment operator, the compiler would not generate the other. This 'all or nothing' rule often led to boilerplate code or unintended behavior.

flowchart TD
    A[Class Definition] --> B{Are Special Member Functions Declared?}
    B -->|No| C[Compiler Implicitly Declares/Defines]
    B -->|Yes| D{Are `= default` or `= delete` Used?}
    D -->|Yes, = default| E[Compiler Generates Default Implementation]
    D -->|Yes, = delete| F[Function is Deleted, Usage is Compile-Time Error]
    D -->|No, Custom Implementation| G[User-Defined Implementation Used]
    C --> H[Object Creation/Manipulation]
    E --> H
    G --> H

Flowchart illustrating compiler behavior for special member functions before and after C++11.

The = default Specifier

The = default specifier explicitly tells the compiler to generate the default implementation for a special member function. This is particularly useful when you've declared other constructors (which would suppress the implicit default constructor) but still want the compiler-generated default. It makes your intent clear and avoids writing an empty function body, which might prevent certain optimizations or trigger warnings.

class MyClass {
public:
    // User-defined constructor
    MyClass(int val) : value(val) {}

    // Explicitly default the default constructor
    // Without this, the compiler would not generate one
    MyClass() = default;

    // Explicitly default the copy constructor
    MyClass(const MyClass&) = default;

    // Explicitly default the copy assignment operator
    MyClass& operator=(const MyClass&) = default;

    // Explicitly default the destructor
    ~MyClass() = default;

private:
    int value;
};

int main() {
    MyClass obj1; // Uses defaulted default constructor
    MyClass obj2(10); // Uses user-defined constructor
    MyClass obj3 = obj2; // Uses defaulted copy constructor
    obj1 = obj3; // Uses defaulted copy assignment operator
    return 0;
}

Using = default for various special member functions.

The = delete Specifier

Conversely, the = delete specifier explicitly tells the compiler that a function should not be generated or used. Any attempt to call a deleted function will result in a compile-time error. This is invaluable for preventing unwanted operations, such as copying objects that manage unique resources (e.g., std::unique_ptr), or ensuring a class is purely move-only. It's also useful for preventing implicit type conversions by deleting specific overloads.

class NonCopyable {
public:
    NonCopyable() = default;

    // Explicitly delete copy constructor and copy assignment operator
    // This makes objects of NonCopyable non-copyable
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;

    // Move constructor and assignment can still be defaulted or custom-defined
    NonCopyable(NonCopyable&&) = default;
    NonCopyable& operator=(NonCopyable&&) = default;
};

class NoHeapAllocation {
public:
    // Prevent heap allocation by deleting operator new
    void* operator new(size_t) = delete;
    void operator delete(void*) = delete;

    NoHeapAllocation() = default;
};

int main() {
    NonCopyable nc1;
    // NonCopyable nc2 = nc1; // Compile-time error: call to deleted copy constructor
    // nc1 = nc2;             // Compile-time error: call to deleted copy assignment operator

    NoHeapAllocation nha;
    // NoHeapAllocation* p_nha = new NoHeapAllocation(); // Compile-time error: call to deleted operator new
    return 0;
}

Using = delete to prevent copying and heap allocation.

The Rule of Zero, Three, and Five

The = default and = delete specifiers simplify the 'Rule of Three/Five' to the 'Rule of Zero'.

  • Rule of Three (pre-C++11): If you define a destructor, copy constructor, or copy assignment operator, you should define all three.
  • Rule of Five (C++11 onwards): If you define any of the Rule of Three functions, you should also consider defining the move constructor and move assignment operator.
  • Rule of Zero (C++11 onwards): If your class does not manage any resources directly (e.g., raw pointers, file handles), then you don't need to declare any of the special member functions. The compiler-generated versions will do the right thing. If your class does manage resources, encapsulate them in a class that follows the Rule of Zero (e.g., std::unique_ptr, std::vector), and then your class can also follow the Rule of Zero.
graph TD
    A[Does Class Manage Resources Directly?] -->|No| B[Rule of Zero: No Special Members Needed]
    A -->|Yes| C[Encapsulate Resource in RAII Wrapper]
    C --> D[Wrapper Follows Rule of Zero/Five]
    D --> B

Decision flow for applying the Rule of Zero.

By explicitly defaulting or deleting special member functions, you communicate your design intent clearly to both the compiler and other developers. This leads to more robust, maintainable, and often more performant C++ code.