uncopyable class와 clone 함수

원작성: 2017.7.4

C++의 경우 class를 생성하면 기본적으로 default constructor와 copy constructor, 대입 연산자가 기본으로 만들어진다. 이 세가지는 프로그래머가 작성하지 않더라도 컴파일러가 알아서 마치 사람이라면 밥 먹고 화장실 가듯이 자연스럽게 만들어 버린다.

C++의 가장 어려운 점이자, OOP(Object Oriented Programming)이라면 어느 언어든 가지고 있는 문제는 객채의 state 관리이다. Class도 하나의 type으로 보므로 각종 사칙 연산의 overloading이 가능한데다, 구조 안에 다양한 class의 객체를 state variable로 가질 수 있어 까딱 잘못하단 지옥문이 열린다. 특히 멀티 쓰레드 환경에선 더 심하다.(덕분에 Functional Programming 추종자들에게 대차게 까이지만, FP도 VM은 C++로 만든게 많다. -_-)

이런 고통을 조금이나마 완화할 수 있는 방법은, class 자체의 복사를 막아버리는 것이다. 물론 다 막으라는 것이 아니라, 한 클래스의 각각의 객체가 서로 독립적인 상태를 가져야 한다든가, copy는 허용하고 싶은데 내용물에 pointer가 있다던가 하는 경우에 고려할만하다. 특히 후자의 pointer를 내용물로 가졌을 때는 앞에 소개할 방법을 심각하게 고려해봐야 한다.

다음과 같은 코드를 실행해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class  
{
public:
B(int i)
{
k = new int;
(*k) = i;
}

int val()
{
return *k;
}

int* valp()
{
return k;
}
private:
int *k;

};

...
B b(1);
B b3 = b;

std::cout << "b : " << b.valp() << std::endl;
std::cout << "b3 : " << b3.valp() << std::endl;

이 코드를 실행하면 다음과 같은 결과를 얻을 수 있다.

1
2
 b : 0047AD50
b3 : 0047AD50

즉, 같은 위치의 메모리 영역을 가리키게 되고, 한 객체에서 메모리 영역 내의 값을 바꾸면 다른 객채까지 영향을 준다.

이런 문제를 해결하기 위해서는 일단 class의 기본적인 복사를 막는다. 다음과 같이 Uncopyable이라는 클래스를 선언하고 copy constructor와 대입연산자를 private으로 선언한다.

1
2
3
4
5
6
7
8
9
10
class Uncopyable
{
public:
Uncopyable() {}
~Uncopyable() {}

private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};

그리고 다음 처럼 위의 클래스를 상속한 클래스를 만들어주자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A : private Uncopyable
{
public:
A(int i)
{
k = i;
}

int val()
{
return k;
}
private:
int k;

};

A a(1);
A a2 = a;

위의 코드를 컴파일하면 Uncopyable 클래스에 대한 access violation이 일어나면서 컴파일이 되지 않는다. 그리고 베이스 클래스에 가상 함수로 clone이라는 것을 지정하여 하위 클래스가 clone을 구현하도록 하면 내가 원하는 방식으로 객체 복사를 할 수 있다.

1
2
3
4
5
6
class Base 
{
public:
...
virtual Base* clone() = 0;
}

개인적으로도 이 방식을 잘 활용하는데, 아직까지는 꽤 성공적인 듯 하다.