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
| Symptom | Check |
|---|---|
| Copy deleted, move also gone | Add explicit = default for move |
| Slicing / partial destroy | virtual ~Base() = default |
Cannot store in vector | Type may be immovable—revisit design |
Deep dive: common mistakes
- Default ctor
private+= defaultwith 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 newdelete)
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
Related posts (internal links)
- 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
Related posts
- Copy/move constructors