C++ =default and =delete: Special Members, ODR, and Rule of Zero

C++ =default and =delete: Special Members, ODR, and Rule of Zero

이 글의 핵심

Use =default to request compiler-generated members and =delete to ban copies, dangerous conversions, and overloads—patterns for safe APIs and RAII.

What are =default and =delete?

They let you explicitly control special member functions.

class MyClass {
public:
    MyClass() = default;              // request default ctor
    MyClass(const MyClass&) = delete; // forbid copy ctor
};

=default

class Point {
private:
    int x, y;
    
public:
    Point() = default;
    
    Point(const Point&) = default;
    Point& operator=(const Point&) = default;
    
    Point(Point&&) = default;
    Point& operator=(Point&&) = default;
    
    ~Point() = default;
};

=delete

class NonCopyable {
public:
    NonCopyable() = default;
    
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
    
    NonCopyable(NonCopyable&&) = default;
    NonCopyable& operator=(NonCopyable&&) = default;
};

Practical examples

Example 1: Singleton (illustrative)

class Singleton {
private:
    static Singleton* instance;
    
    Singleton() = default;
    
public:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;
    
    static Singleton* getInstance() {
        if (!instance) {
            instance = new Singleton();
        }
        return instance;
    }
};

Singleton* Singleton::instance = nullptr;

Example 2: Resource handle

class FileHandle {
private:
    FILE* file;
    
public:
    explicit FileHandle(const char* filename) {
        file = fopen(filename, "r");
    }
    
    ~FileHandle() {
        if (file) {
            fclose(file);
        }
    }
    
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
    
    FileHandle(FileHandle&& other) noexcept : file(other.file) {
        other.file = nullptr;
    }
    
    FileHandle& operator=(FileHandle&& other) noexcept {
        if (this != &other) {
            if (file) {
                fclose(file);
            }
            file = other.file;
            other.file = nullptr;
        }
        return *this;
    }
};

Example 3: Banning a conversion

class SafeInt {
private:
    int value;
    
public:
    SafeInt(int v) : value(v) {}
    
    SafeInt(double) = delete;  // block double -> SafeInt accidents
    
    int getValue() const {
        return value;
    }
};

int main() {
    SafeInt x(10);      // OK
    // SafeInt y(3.14); // error: deleted ctor
}

Example 4: No heap allocation

#include <cstddef>

class StackOnly {
public:
    StackOnly() = default;

    void* operator new(std::size_t) = delete;
    void* operator new[](std::size_t) = delete;
    void operator delete(void*) = delete;
    void operator delete[](void*) = delete;
};

int main() {
    StackOnly obj;  // OK: automatic storage
    // auto* p = new StackOnly();  // error
}

Note: std::vector<StackOnly> and similar may still be constrained because allocators can require constructible storage—document intent.

Rule of Five

class Resource {
private:
    int* data;
    
public:
    Resource(int value) : data(new int(value)) {}
    
    ~Resource() {
        delete data;
    }
    
    Resource(const Resource& other) 
        : data(new int(*other.data)) {}
    
    Resource& operator=(const Resource& other) {
        if (this != &other) {
            delete data;
            data = new int(*other.data);
        }
        return *this;
    }
    
    Resource(Resource&& other) noexcept 
        : data(other.data) {
        other.data = nullptr;
    }
    
    Resource& operator=(Resource&& other) noexcept {
        if (this != &other) {
            delete data;
            data = other.data;
            other.data = nullptr;
        }
        return *this;
    }
};

Rule of Zero

class Resource {
private:
    std::unique_ptr<int> data;
    
public:
    Resource(int value) : data(std::make_unique<int>(value)) {}
};

Common pitfalls

Pitfall 1: Delete copy, move disappears

class MyClass {
public:
    MyClass(const MyClass&) = delete;
    MyClass& operator=(const MyClass&) = delete;
    // move may be implicitly deleted
};

class MyClass {
public:
    MyClass(const MyClass&) = delete;
    MyClass& operator=(const MyClass&) = delete;
    
    MyClass(MyClass&&) = default;
    MyClass& operator=(MyClass&&) = default;
};

Pitfall 2: =default with uninitialized members

class MyClass {
    int x;  // indeterminate if not initialized
    
public:
    MyClass() = default;
};

class MyClass {
    int x = 0;
    
public:
    MyClass() = default;
};

Pitfall 3: Non-virtual destructor in hierarchy

class Base {
public:
    ~Base() = default;  // not virtual
};

class Base {
public:
    virtual ~Base() = default;
};

Overload control

void func(int x) {
    std::cout << "int: " << x << std::endl;
}

void func(double x) = delete;

int main() {
    func(10);      // OK
    // func(3.14); // error
}

Deleted template specialization

template<typename T>
void process(T value) {
    std::cout << "generic" << std::endl;
}

template<>
void process<double>(double) = delete;

int main() {
    process(10);      // OK
    // process(3.14); // error
}

default vs explicit bodies

class PointA {
    int x = 0, y = 0;
public:
    PointA() = default;
};

class PointB {
    int x, y;
public:
    PointB() : x(0), y(0) {}
};

Deep dive: =default with noexcept and triviality

If move operations are not noexcept, containers may copy on reallocation. When moves cannot throw, prefer noexcept with = default.

struct Buffer {
    Buffer(Buffer&&) noexcept = default;
    Buffer& operator=(Buffer&&) noexcept = default;
};

Trivial types optimize well; = default signals “nothing special here” to reviewers.


Deep dive: =default on abstract interfaces

Even interface-like classes can use = default on a virtual destructor:

struct IPlugin {
    virtual ~IPlugin() = default;
    virtual void run() = 0;
};

Deep dive: =delete to block bad conversions

struct Index {
    explicit Index(int v) : v_(v) {}
    Index(double) = delete;
private:
    int v_{};
};

Deep dive: size and performance

Deleted functions impose no body in the binary—purely compile-time bans. = default often matches or beats hand-written trivial members.


Deep dive: debugging table

SymptomCheck
Copy deleted, move also goneAdd explicit = default for move
Slicing / partial destroyvirtual ~Base() = default
Cannot store in vectorType may be immovable—revisit design

Deep dive: common mistakes

  • Default ctor private + = default with no other ctors—maybe unusable outside class—verify intent.
  • Partially deleted operator=—other assignments may still exist—review Rule of Five.
  • User destructor without considering copy/move—update for Rule of Five/Zero.

Deep dive: thread handle style wrapper

class UniqueHandle {
    void* h_{nullptr};
public:
    explicit UniqueHandle(void* h) : h_(h) {}
    ~UniqueHandle() { /* close(h_) */ }

    UniqueHandle(const UniqueHandle&) = delete;
    UniqueHandle& operator=(const UniqueHandle&) = delete;

    UniqueHandle(UniqueHandle&& o) noexcept : h_(o.h_) { o.h_ = nullptr; }
    UniqueHandle& operator=(UniqueHandle&& o) noexcept {
        if (this != &o) {
            /* close(h_) */
            h_ = o.h_;
            o.h_ = nullptr;
        }
        return *this;
    }
};

FAQ

Q1: When to use =default?

A:

  • Express “compiler-generated is fine”
  • After defining other ctors you still need defaults
  • Keep types trivial when possible

Q2: When to use =delete?

A:

  • Forbid copy/move
  • Forbid dangerous conversions
  • Forbid heap use (operator new delete)

Q3: Rule of Five vs Zero?

A:

  • Five: you manage resources directly
  • Zero: delegate to smart pointers / containers (preferred when possible)

Q4: Copy deleted—what about move?

A: Often implicitly deleted—re-enable move explicitly if that is the design.

Q5: Benefits of =default?

A:

  • Clear intent
  • Good codegen for trivial types

Q6: Resources?

A:

  • Effective Modern C++
  • cppreference.com
  • C++ Primer

  • explicit keyword
  • Initializer-list constructor

Practical tips

Debugging

  • Warnings first

Performance

  • Profile

Code review

  • Conventions

Practical checklist

Before coding

  • Right approach?
  • Maintainable?
  • Performance?

While coding

  • Warnings?
  • Edge cases?
  • Errors?

At review

  • Intent?
  • Tests?
  • Docs?

Keywords

C++, default, delete, special member functions, C++11


  • Copy/move constructors