다음과 같은 클래스가 있다고 가정한다.

struct Obj {
	Obj(const char* p) {
		size_t len = strlen(p);
		p_ = new char[len + 1];
		strncpy(p_, p, len + 1);
	}
	~Obj() {
		if (p_ != nullptr) {
			delete[] p_;
			p_ = nullptr;
		}
	}
protected:
	char* p_;
};

constructor에서 p_ 라는 변수를 할당(new)하고 복사(strncpy)를 한다. 당연히 destructor에서는 p_의 값을 확인하여 nullptr가 아닌 경우 해제(delete)를 해도록 한다.

첫 번째 문제점. 다음과 같은 코드가 있다고 가정을 하자. 이 경우 “Obj b(a);”라는 코드에서 객체의 copy가 이루어 지면서 2개의 포인터가 같은 곳을 가르키게 되어 2개의 객체가 해제될 때 double free의 오류가 발생한다.

Obj a("hello world");
Obj b(a); // copy constructor

따라서 다음과 같은 copy constructor를 명시해 줘야 한다.

Obj(const Obj& r) {
  size_t len = strlen(r.p_);
  p_ = new char[len + 1];
  strncpy(p_, r.p_, len + 1);
}

두 번째 문제점. “b = a;”라는 코드에서 객체가 복사된 이후 해제될 때에도 위와 같은 똑같은 double free의 오류가 발생할 수 있다.

Obj a("hello world");
Obj b;
b = a; // copy assign operator

따라서 다음과 같은 copy assignment operator를 명시해 줘야 한다.

Obj& operator = (const Obj& r) {
  size_t len = strlen(r.p_);
  p_ = new char[len + 1];
  strncpy(p_, r.p_, len + 1);
  return *this;
}

이와 같이 객체의 copy(shallow copy)가 이루어지는 경우 dangling pointer에 의한 double free와 같은 오류를 방지하기 위하여 destructor, copy constructor, copy assignment operator 3개의 memeber function을 한꺼번에 명시하도록 해야 한다. 이를 “Rule of three”라고 한다.

마찬가지로 “Rule of three”에서 move constructor, move assignment operator까지 더하여 5개의 function을 한꺼번에 명시하도록 하는 것을 “Rule of five”라고 한다.

클래스를 디자인할 때 destructor, copy constructor, copy assignment operator[, move constructor, move assignment operator]는 반드시 모두 명시하거나 모두 생략하거나 해야 한다.

g++ 컴파일러가 버전업되면서 이러한 rule을 지켜주지 않는 경우 warning을 띄우도록 변경되었다.

// CC가 없다(CC를 명시해야 한다)는 뜻
warning: implicitly-declared ‘constexpr Obj::Obj(const Obj&)’ is deprecated

// CA가 없다(CA를 명시해야 한다)는 뜻
warning: implicitly-declared ‘Obj& Obj::operator=(const Obj&)’ is deprecated

Test code

CC(copy constructor)와 CA(copy assignment operator)의 명시 여부에 따른 warning 여부를 테스트해 보았다 (g++ (Debian 9.3.0-11) 9.3.0).

  • CC와 CA가 둘 다 없거나 둘 다 존재하는 경우에는 warning이 뜨지 않음.
  • CC와 CA 중 하나만 명시하는 경우 생략된 function을 호출할 때 warning이 뜸.
struct Obj {
  Obj()                        {} // DC(default constructor)
  Obj(const Obj&)              {} // CC(copy constructor) ---A
  Obj& operator = (const Obj&) { return *this; } // CA(copy assignment operator) ---B
};

int main() {
  // copy constructor test
  {
    Obj a;
    Obj b(a); // use of CC ---C
  }

  // copy assignment operator test
  {
    Obj a;
    Obj b;
    a = b; // use of CA ---D
  }
}
CC(A) CA(B) use of CC(C) use of CA(D)
X X X O
X X X X
X X O X
X X O O
X O X X
X O X O
X O O(warning) X
X O O(warning) X
O X X X
O X X O(warning)
O X O O(warning)
O X O X
O O X X
O O X O
O O O X
O O O O

References

https://en.cppreference.com/w/cpp/language/rule_of_three