Reference Counting

상황

addRef(), release()refcnt를 관리하고 있었을 때 실수로 delete하는 것을 막고 싶다. 지역 변수를 만들고 release()하는 것을 막고 싶다.

대응

  • Protected destructor 사용
  • 외부에서 직접 delete하는 것을 방지
  • 객체를 힙에만 생성 가능하게 설계

예시

  class Car {
    int refcnt = 0;
public:
    void addRef() { ++refcnt; }
    void release() {
        if (--refcnt == 0)
            delete this;
    }
protected:
    ~Car() { std::cout << "~Car" << std::endl; }
};

int main() {
    // Car c;
    // c.release();
    Car* p1 = new Car;
    p1->addRef();
    // delete p1;
    p1->release();
}
  

Reference Counting 2

상황

Base가 되는 RefCount 클래스를 만들어서 관리하고 싶다. 하지만 그렇게 하면 Derived 클래스의 소멸자가 호출되지 않는 문제 발생.

대응

  • **CRTP(Curiously Recurring Template Pattern)**으로 해결
  • addRef, releaseconst 함수로 만들고
  • refcnt를 **mutable**로 선언
  • const 함수 안에서는 thisconst로 취급되므로 const_cast 필요
  • template hoisting/thin template 적용하여 code bloat 방지

예시

  class RefCountBase {
protected:
    mutable int refcnt = 0;
public:
    void addRef() const { ++refcnt; }
};

template<typename T>
class RefCount {
public:
    void release() const {
        if (--refcnt == 0)
            delete static_cast<const T*>(this);
    }
};
  

참고

  • std::atomic<int> refcnt로 멀티스레드 환경 대응 가능
  • ++refcntrefcnt.fetch_add(1, std::memory_order_relaxed)
  • --refcntrefcnt.fetch_sub(1, std::memory_order_acq_rel)

멀티스레드 프로그래밍 시 memory ordering도 고려해야 한다.

Reference Counting 3

shared_ptr의 문제점

  • 참조계수를 관리하는 관리 객체가 2개 이상 생성될 위험

예시

  class Truck {
public:
    ~Truck() { std::cout << "~Truck" << std::endl; }
};

int main() {
    std::shared_ptr<Truck> sp1(new Truck);
    std::shared_ptr<Truck> sp2 = sp1;

    Truck* p1 = new Truck;
    std::shared_ptr<Truck> sp3(p1);
    std::shared_ptr<Truck> sp4(p1); // 문제: sp3, sp4가 각각 별도의 관리 객체 생성
}
  

std::make_shared를 사용하면 참조계수가 관리 객체 안에 포함되도록 할 수 있지만, 이걸 원하지 않는 경우 별도의 refcount가 포함된 클래스를 만들어서 사용하기도 한다.

std::advance

상황

반복자를 5칸 전진하고 싶다.

  • p = p + 5vector에서는 가능하지만 list에서는 불가능
  • ++p; ++p; ++p; ++p; ++p;는 되지만 비효율적

대응

std::advance(p, 5) 사용

  • vector에서는 p = p + 5처럼 동작
  • list에서는 ++p 여러 번 반복처럼 동작

구현 방법의 변화

std::advance의 구현 방법은 C++ 표준에 따라 변화:

  • C++98: tag dispatching
  • C++11: enable_if
  • C++17: if constexpr
  • C++20: conceptrequires clauses

이를 통해 시대별 C++의 코딩 방식 변화와 함께 설계 원리를 이해할 수 있다.