Namespaces
Variants

The rule of three/five/zero

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

목차

Rule of three (삼의 법칙)

클래스에 사용자 정의 destructor , 사용자 정의 copy constructor , 또는 사용자 정의 copy assignment operator 가 필요하다면, 거의 항상 세 가지 모두가 필요합니다.

C++는 다양한 상황에서(값에 의한 전달/반환, 컨테이너 조작 등) 사용자 정의 타입의 객체를 복사하고 복사 할당하기 때문에, 이러한 특별 멤버 함수들은 접근 가능할 경우 호출되며, 사용자 정의되지 않은 경우 컴파일러에 의해 암시적으로 정의됩니다.

암시적으로 정의된 특수 멤버 함수들은 클래스가 비클래스 타입(원시 포인터, POSIX 파일 디스크립터 등)의 핸들을 가진 리소스를 관리하는 경우 사용되어서는 안 되며, 이때 소멸자는 아무 작업도 수행하지 않고 복사 생성자/대입 연산자는 "얕은 복사"(내부 리소스를 복제하지 않고 핸들의 값만 복사)를 수행합니다.

#include <cstddef>
#include <cstring>
#include <iostream>
#include <utility>
class rule_of_three
{
    char* cstring; // 동적으로 할당된 메모리 블록을 핸들로 사용하는
                   // 원시 포인터
public:
    explicit rule_of_three(const char* s = "") : cstring(nullptr)
    {   
        if (s)
        {   
            cstring = new char[std::strlen(s) + 1]; // 할당
            std::strcpy(cstring, s); // 데이터 복사
        }
    }
    ~rule_of_three() // I. 소멸자
    {
        delete[] cstring; // 할당 해제
    }
    rule_of_three(const rule_of_three& other) // II. 복사 생성자
        : rule_of_three(other.cstring) {}
    rule_of_three& operator=(const rule_of_three& other) // III. 복사 할당 연산자
    {
        // 간결함을 위해 복사-후-교체 방식으로 구현
        // 이로 인해 잠재적인 저장소 재사용이 방지됨
        rule_of_three temp(other);
        std::swap(cstring, temp.cstring);
        return *this;
    }
    const char* c_str() const // 접근자
    {
        return cstring;
    }
};
int main()
{
    rule_of_three o1{"abc"};
    std::cout << o1.c_str() << ' ';
    auto o2{o1}; // II. 복사 생성자 사용
    std::cout << o2.c_str() << ' ';
    rule_of_three o3("def");
    std::cout << o3.c_str() << ' ';
    o3 = o2; // III. 복사 할당 사용
    std::cout << o3.c_str() << '\n';
}   // I. 모든 소멸자가 여기서 호출됨

출력:

abc abc def abc

복사 가능한 핸들을 통해 복사 불가능한 자원을 관리하는 클래스는 복사 할당과 복사 생성자를 private 으로 선언하고 그 정의를 제공하지 않아야 할 수 있습니다 (C++11 이전) 복사 할당과 복사 생성자를 = delete 로 정의해야 할 수 있습니다 (C++11 이후) . 이것은 rule of three의 또 다른 적용입니다: 하나를 삭제하고 다른 하나를 암시적으로 정의되도록 남겨두는 것은 일반적으로 올바르지 않습니다.

다섯의 법칙

사용자 정의( = default 또는 = delete 로 선언된 포함) 소멸자, 복사 생성자, 복사 할당 연산자의 존재는 move constructor move assignment operator 의 암시적 정의를 방지하므로, 이동 의미론이 필요한 모든 클래스는 다섯 가지 특별 멤버 함수를 모두 선언해야 합니다:

class rule_of_five
{
    char* cstring; // 동적으로 할당된 메모리 블록을 핸들로 사용하는
                   // 원시 포인터
public:
    explicit rule_of_five(const char* s = "") : cstring(nullptr)
    { 
        if (s)
        {
            cstring = new char[std::strlen(s) + 1]; // 할당
            std::strcpy(cstring, s); // 데이터 복사
        } 
    {
    ~rule_of_five()
    {
        delete[] cstring; // 할당 해제
    {
    rule_of_five(const rule_of_five& other) // 복사 생성자
        : rule_of_five(other.cstring) {}
    rule_of_five(rule_of_five&& other) noexcept // 이동 생성자
        : cstring(std::exchange(other.cstring, nullptr)) {}
    rule_of_five& operator=(const rule_of_five& other) // 복사 할당
    {
        // 간결함을 위해 임시 복사본으로부터의 이동 할당으로 구현됨
        // 이는 잠재적인 저장소 재사용을 방지함에 유의
        return *this = rule_of_five(other);
    {
    rule_of_five& operator=(rule_of_five&& other) noexcept // 이동 할당
    {
        std::swap(cstring, other.cstring);
        return *this;
    {
// 또는, 두 할당 연산자를 모두 복사 후 교체 구현으로 대체할 수 있으며,
// 이 또한 복사 할당에서 저장소 재사용에 실패합니다.
//  rule_of_five& operator=(rule_of_five other) noexcept
//  {
//      std::swap(cstring, other.cstring);
//      return *this;
//  }
{;

Rule of Three와 달리, 이동 생성자와 이동 할당 연산자를 제공하지 않는 것은 일반적으로 오류가 아닌 최적화 기회를 놓치는 것입니다.

제로의 법칙

사용자 정의 소멸자, 복사/이동 생성자 또는 복사/이동 할당 연산자를 갖는 클래스는 소유권 전담을 처리해야 합니다(이는 단일 책임 원칙 에서 비롯됩니다). 다른 클래스들은 사용자 정의 소멸자, 복사/이동 생성자 또는 복사/이동 할당 연산자를 갖지 않아야 합니다 [1] .

이 규칙은 C++ 핵심 가이드라인에서도 C.20: 기본 연산 정의를 피할 수 있다면 피하라 로 등장합니다.

class rule_of_zero
{
    std::string cppstring;
public:
    rule_of_zero(const std::string& arg) : cppstring(arg) {}
};

다형적으로 사용될 목적의 기본 클래스인 경우, 해당 소멸자는 public 이고 virtual 으로 선언되어야 할 수 있습니다. 이는 암시적 이동을 차단하고(그리고 암시적 복사를 사용 중단으로 표시함), 따라서 특수 멤버 함수들은 = default [2] 로 정의되어야 합니다.

class base_of_five_defaults
{
public:
    base_of_five_defaults(const base_of_five_defaults&) = default;
    base_of_five_defaults(base_of_five_defaults&&) = default;
    base_of_five_defaults& operator=(const base_of_five_defaults&) = default;
    base_of_five_defaults& operator=(base_of_five_defaults&&) = default;
    virtual ~base_of_five_defaults() = default;
};

그러나 이는 클래스를 슬라이싱에 취약하게 만들기 때문에, 다형성 클래스들은 종종 복사를 = delete 로 정의합니다(이는 C++ 코어 가이드라인의 C.67: 다형성 클래스는 public 복사/이동을 억제해야 한다 를 참조하십시오). 이로 인해 Rule of Five에 대한 다음과 같은 일반적인 문구가 도출됩니다:

C.21: 복사, 이동, 소멸자 함수 중 하나를 정의하거나 =delete한다면, 모두 정의하거나 =delete하라.

외부 링크

  1. "Rule of Zero", R. Martinho Fernandes 08/15/2012
  2. "A Concern about the Rule of Zero", Scott Meyers, 3/13/2014 .