On this page
article
C++ Reference Counting
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
,release
를const
함수로 만들고refcnt
를 **mutable
**로 선언const
함수 안에서는this
도const
로 취급되므로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
로 멀티스레드 환경 대응 가능++refcnt
→refcnt.fetch_add(1, std::memory_order_relaxed)
--refcnt
→refcnt.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 + 5
는vector
에서는 가능하지만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:
concept
및requires
clauses
이를 통해 시대별 C++의 코딩 방식 변화와 함께 설계 원리를 이해할 수 있다.