[C++] placement new 객체 태그 만들기
큰 버퍼를 미리 할당하고 placement new를 사용하는 경우가 있습니다.
기본적으로, placment new 에 전달되는 주소는 객체의 필드가 캐시라인에 걸치지 않게 해야합니다.
이래야 atomic / SIMD 연산시 torn access 가 발생하지 않습니다.
구현 해 볼 것은 다음과 같은 사항을 만족하는 클래스 디자인 입니다.
1. 버퍼를 참조하는 객체가 없어질 때까지 버퍼는 유효해야합니다.
2. 객체의 delete 호출시 메모리가 해제 되어서는 안됩니다.
3. 객체의 소멸자가 먼저 호출 되고, 버퍼의 참조를 해제해야 합니다.
템플릿을 이용해서 구성하면 간단하게 구현이 가능합니다.
template <typename T> class Placed; class PlacedTag { template <typename T> friend class Placed; private: PlacedTag(const std::shared_ptr<MyBuffer>& ref) : m_Ref(new std::shared_ptr<MyBuffer>(ref)) {} virtual ~PlacedTag() { delete m_Ref; } private: //반드시 shared_ptr의 포인터를 사용해야 합니다. std::shared_ptr<MyBuffer>* m_Ref; }; template <typename Entity> class Placed : public PlacedTag, public Entity //상속 된 순서로 초기화, 그 역의 순으로 정리 됩니다. { public: template<typename... Args> Placed(const std::shared_ptr<MyBuffer>& ref, const Args&... args) : PlacedTag(ref), Entity(args...) {} public: virtual ~Placed() {} void operator delete(void* ptr) { (void)ptr; } };
operator delete 를 재정의해서 객체 주소에 대해서 free를 호출하지 않도록 합니다.
그런 다음 이 객체의 소멸자가 호출될 텐데요,
객체의 소멸은 다음과 같은 순서로 이루어 집니다.
소멸자 호출 -> 필드(데이터 멤버)의 소멸자 호출 -> 상위 클래스에 대해서 똑같은 작업 수행
따라서, 다중 상속시 태그를 먼저 두고, 실 사용 타입을 그 다음에 둡니다.
이러면 실 사용 타입이 먼저 정리되고 나서야, 참조를 반납합니다.
태그에서 참조의 포인터를 사용하는 구문이 있습니다.
만약 포인터가 아니라 이런 구현이라면 문제가 발생합니다.
class PlacedTag { template <typename T> friend class Placed; private: PlacedTag(const std::shared_ptr<MyBuffer>& ref) : m_Ref(ref) {} virtual ~PlacedTag() { } private: //위험 ! const std::shared_ptr<MyBuffer> m_Ref; };
태그 소멸시 버퍼에 대한 참조를 반납하게 되는데,
만약 버퍼에 대한 마지막 참조를 반납하는 상황이라면,
MyBuffer 구현에 따라 shared_ptr 소멸 도중 객체가 위치한 블럭이 다른 값으로 채워지게 됩니다.




_Destroy 에서 버퍼 객체가 delete 됩니다.
그런데 _Destroy 이후에 _Decwref 를 호출합니다. (weak ref 가 0면 컨트롤 블럭 자체를 해제하는 과정입니다.)
이러면 버퍼의 값이 다른 값으로 오염된 상황이 올 수 있는 것이고,
그러면 shared_ptr 내부의 컨트롤 블럭 주소가 다른 값으로 오염되었을 가능성이 있습니다.
이런 상황에서 컨트롤 블럭을 해제합니다.
결국 오염된 주소에 접근해서 delete를 합니다.
다음과 같이 포인터를 필드로 가지면 주소의 값이 스택프레임 내에서 안전하기 때문에 별 상관이 없습니다.
class PlacedTag { template <typename T> friend class Placed; private: PlacedTag(const std::shared_ptr<MyBuffer>& ref) : m_Ref(new std::shared_ptr<MyBuffer>(ref)) {} virtual ~PlacedTag() { delete m_Ref; } private: //반드시 shared_ptr의 포인터를 사용해야 합니다. std::shared_ptr<MyBuffer>* m_Ref; };

이상 마치겠습니다.
감사합니다.
'C++' 카테고리의 다른 글
[C++] std::apply 구현해보기 (0) | 2025.06.05 |
---|---|
[C++] functor 만들기 (1) | 2025.06.04 |
[C++] std::move 와 std::forward (0) | 2022.04.09 |
[C++] STL Custom Allocator 사용 (0) | 2022.04.08 |
[C++] new[] , delete[] 오버로딩시 주의 사항. (0) | 2022.03.28 |
댓글
이 글 공유하기
다른 글
-
[C++] std::apply 구현해보기
[C++] std::apply 구현해보기
2025.06.05 -
[C++] functor 만들기
[C++] functor 만들기
2025.06.04 -
[C++] std::move 와 std::forward
[C++] std::move 와 std::forward
2022.04.09 -
[C++] STL Custom Allocator 사용
[C++] STL Custom Allocator 사용
2022.04.08
댓글을 사용할 수 없습니다.