[2026] C++ =default and =delete: Special Members, ODR, and Rule of Zero
이 글의 핵심
C++11 =default and =delete: controlling special members, non-copyable types, deleted conversions, heap-only types, Rule of Five vs Rule of Zero, and noexcept moves.
What are =default and =delete?
They let you explicitly control special member functions. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
class MyClass {
public:
MyClass() = default; // request default ctor
MyClass(const MyClass&) = delete; // forbid copy ctor
};
=default
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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)
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
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
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
class Base {
public:
~Base() = default; // not virtual
};
class Base {
public:
virtual ~Base() = default;
};
Overload control
아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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.
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 타입 정의
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:
다음은 간단한 cpp 코드 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
struct IPlugin {
virtual ~IPlugin() = default;
virtual void run() = 0;
};
Deep dive: =delete to block bad conversions
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
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
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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)
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?