본문 바로가기

Library/C/C++

커스텀 할당자를 작성할 때 construct() 호출 문제

C++ 커스텀 할당자를 작성할 때 구현하기 까다로운 부분 중 하나는 std::allocator의 construct()와 destory() 멤버 함수이다. 이들은 개체가 생성되거나 파괴될 때 명시적으로 개체의 생성자와 소멸자를 호출하는 함수들이고, destroy()의 경우 명시적으로 소멸자를 호출하는 것이 전부이다. 문제는 construct()이다. construct()는 replacement new를 사용하여 확보한 메모리 공간에 개체를 실제로 생성하여 배치하는 역할을 한다. 그런데, 개체가 초기값 없이 생성될 경우, construct()는 항상 개체가 생성될 때의 초기값을 요구하기 때문에 construct()를 일반적으로 사용하기는 어렵다.

 

allocator<dummy> alloc;

dummy *p = alloc.allocate(sizoef(dummy));

new(static_cast<dummy *>p) dummy();

 

이 코드는 dummy 클래스의 생성자를 정상적으로 호출한다. construct()를 사용하여 동일한 결과를 얻고자 한다면, 해당 코드는 다음과 같다.

 

allocator<dummy> alloc;

dummy *p = alloc.allocate(sizeof(dummy));

alloc.construct(p, dummy());

 

그러나, 이 코드는 의도하지 않은 결과를 출력한다. construct() 함수는 두 번째 인자로 개체의 초기값을 요구하며, 여기에 직접 dummy()를 넣어줄 경우 construct()의 실행이 종료되면서 dummy()의 소멸자가 호출된다. 즉, 이렇게 코드를 작성할 경우 destroy()에서 소멸자가 두 번 호출된다. 이것은 dummy() 클래스의 생성자가 인자 없이 정의되었다면, construct()를 사용하여 개체의 생성자를 호출할 방법이 없다는 것을 의미한다.

 

class dummy {

public:

dummy() { std::cout<<"ctor"<<std::endl; }

~dummy() { std::cout<<"dtor"<<std::endl; }

};

 

위와 같이 dummy가 정의되었다면, construct()를 사용하여 dummy를 한번만 생성하며 생성자를 호출할 방법은 없다.

 

std::allocator<dummy> alloc;

dummy *p = alloc.allocate(sizeof(dummy));

alloc.construct(p, *p);

 

위와 같이 코드를 작성하더라도 실제로 dummy의 생성자는 호출되지 않으며, construct()를 사용하여 dummy 개체를 생성하기 위해서는 반드시 인자가 정의된 생성자가 필요하다. 예를 들어, dummy 개체가 다음과 같은 생성자를 가질 경우,

 

class dummy {

public:

dummy(int) { std::cout<<"ctor"<<std::endl; }

~dummy() { std::cout<<"dtor"<<std::endl; }

};

 

다음 코드는 원하는 결과를 얻을 수 있다.

 

std::allocator <dummy> alloc;

dummy *p = alloc.allocate(sizeof(dummy));

alloc.construct(p, dummy(0));

 

그러나, 위에서 설명한 이 문제 때문에 초기값을 사용하는 생성자가 정의되지 않은 개체의 construct(), destroy()를 사용하여 개체를 생성하는 방법은 표준이 아니며, SGI STL를 비롯한 대부분의 구현은 construct(), destroy() 대신 직접 replacement new를 사용하고 명시적으로 소멸자를 호출한다. std::vector가 해당 개체를 생성할 때 반드시 초기값을 요구하는 것을 생각해보라. C++11 표준에서는 가변 템플릿을 사용하여 이 문제를 대응하고 있기 때문에, C++11 표준을 사용하여 커스텀 할당자를 작성한다면 초기값을 사용하는 생성자를 가지지 않는 개체라도 construct(), destroy()를 사용하는 것은 문제를 일으키지 않을 것이다.