«   2024/04   »
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 30
Archives
Today
Total
04-24 04:41
관리 메뉴

lancelot.com

reference counting 본문

프로그래밍

reference counting

lancelot50 2022. 7. 29. 21:59
  • 소멸자를 proteced 로하면 객체를 힙에만 생성할 수 있다.
#include<iostream>

class RefCount
{
	int refcnt = 0;

public :
	void addRef() { ++refcnt; }
	void release()
	{
		if (--refcnt == 0)
			delete this;
	}
// 소멸자를 protected로 하면 외부에서 소멸이 불가능해져서
// 1. delete 불가능 (소멸자 호출이 불가능하므로)
// 2. 지역변수 생성 불가 (지역변수가 소멸될때 소멸자를 호출 할 수 없어서) - 객체를 힙에만 생성할 수 있다.
protected:
	virtual ~RefCount()	{	std::cout << "~RefCount" << std::endl; }
};

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

int main()
{
	Truck* p1 = new Truck;
	p1->addRef();
	p1->release();
}

 

  • reference counting class 가 virtual function table을 가지고 있으면 메모리를 더 차지하므로 ~RefCount 의 virtual 을 제거하고싶다 - 가상 소멸자 -> CRTP
  • CRTP
    • Curiously Recurring Template Pattern
    • 파생클래스가 템플릿인자를 통해 기반클래스에 자신의 클래스 이름을 전달하는 테크닉
template<typename T>
class RefCount
{
	int refcnt = 0;

public :
	void addRef() { ++refcnt; }
	void release()
	{
		// 파생클래스를 delete 하지 못하는 이유는 파생클래스 타입을 알수 없기때문
		// 따라서 파생클래스를 템플릿 인자로 전달받으면 가능하다
		if (--refcnt == 0)
			delete static_cast<T*>(this);
	}
// 소멸자를 protected로 하면 외부에서 소멸이 불가능해져서
// 1. delete 불가능 (소멸자 호출이 불가능하므로)
// 2. 지역변수 생성 불가 (지역변수가 소멸될때 소멸자를 호출 할 수 없어서) - 객체를 힙에만 생성할 수 있다.
protected:
	~RefCount()	{	std::cout << "~RefCount" << std::endl; }
};

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

int main()
{
	Truck* p1 = new Truck;
	p1->addRef();
	p1->release();
}

 

  • const pointer를 생성했을 때도 reference counting 이 작동해야한다
#include<iostream>

// CRTP
// Curiously Recurring Template Pattern
// 파생클래스가 템플릿인자를 통해 기반클래스에 자신의 클래스 이름을 전달

template<typename T>
class RefCount
{
	mutable int refcnt = 0;		// 상수 멤버함수에서도 수정가능한 멤버로 만들어주는 mutable

public :
	void addRef() const { ++refcnt; }
//	void release()			// void release(RefCount* this)
	void release() const	// void release(const RefCoutn* this)
	{
		if (--refcnt == 0)
//			delete static_cast<T*>(this);			// 캐스팅 error
//			delete static_cast<T*>(const_cast<RefCount*>(this));// ok
			delete static_cast<const T*>(this);		// ok
	}
// 소멸자를 protected로 하면 외부에서 소멸이 불가능해져서
// 1. delete 불가능 (소멸자 호출이 불가능하므로)
// 2. 지역변수 생성 불가 (지역변수가 소멸될때 소멸자를 호출 할 수 없어서) - 객체를 힙에만 생성할 수 있다.
protected:
	~RefCount()	{	std::cout << "~RefCount" << std::endl; }
};

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

int main()
{
	const Truck* p1 = new Truck;	// p1을 통해서는 읽기만 하겠다.
					// 상수 객체는 상수 멤버함수만 호출할 수 있다.
					// 상수 객체도 수명은 관리할 수 있어야한다.
	p1->addRef();
	p1->release();
}

 

  • Truck, Car 등이 계속늘어나면, 중복된 코드메모리가 많이 늘어난다.  이것을 줄일 수 없을까?
  • template hoisting
    • 템플릿이 동일한 코드를 여러개 만드는 것을 방지
    • 템플릿 인자에 의존적이지 않은 멤버는 템플릿이 아닌 기반클래스를 만들어서 별도로 제공
    • thin template
#include<iostream>

class RefCountBase
{
protected:
	mutable int refcnt = 0;		// 상수 멤버함수에서도 수정가능한 멤버로 만들어주는 mutable

public:
	void addRef() const { ++refcnt; }
};

template<typename T>
class RefCount : public RefCountBase
{
public :
//	void release()			// void release(RefCount* this)
	void release() const	// void release(const RefCoutn* this)
	{
		if (--refcnt == 0)
//			delete static_cast<T*>(this);				// 캐스팅 error
//			delete static_cast<T*>(const_cast<RefCount*>(this));	// ok
			delete static_cast<const T*>(this);			// ok
	}
};


// Truck, Car 등이 계속늘어나면, 중복된 코드메모리가 많이 늘어난다.
//  이것을 줄일 수 없을까?
// template hoisting
//  -> 템플릿이 동일한 코드를 여러개 만드는 것을 방지
//  -> 템플릿 인자에 의존적이지 않은 멤버는 템플릿이 아닌 기반클래스를 만들어서 별도로 제공
//  -> thin template

class Truck : public RefCount<Truck>{};
class Car : public RefCount<Car> { };

int main()
{
	// p1을 통해서는 읽기만 하겠다.
	// 상수 객체는 상수 멤버함수만 호출할 수 있다.
	// 상수 객체도 수명은 관리할 수 있어야한다.
	const Truck* p1 = new Truck;		
	p1->addRef();
	p1->release();
}

 

  • C++11 에 도입된 thread safe 한 atomic 을 적용
#include<iostream>
#include<atomic>

// C++11 에 도입된 threadsafe 한 atomic을 적용.
class RefCountBase
{
protected:
	mutable std::atomic<int> refcnt = {0};

public:
	void addRef() const 
	{
//		++refcnt;
		refcnt.fetch_add(1, std::memory_order_relaxed);
	}
};

template<typename T>
class RefCount : public RefCountBase
{
public :
	void release() const	// void release(const RefCoutn* this)
	{
		int ret = refcnt.fetch_sub(1, std::memory_order_acq_rel);// refcnt 에서 1을 빼고, 이전값을 return
		if (refcnt == 1)
			delete static_cast<const T*>(this);
	}
};

int main()
{
	const Truck* p1 = new Truck;		
	p1->addRef();
	p1->release();
}

 

  • 포인터가 생성/ 복사 될때마다 수동으로 AddRef를 부르는 것이 힘들다.
  • 포인터가 객체라면 
    • 생성자에서 addref
    • 복사 생성자에서 addref
    • 소멸자에서 release를 호출해주면, 자동화 가능
#include<iostream>
#include<atomic>

// C++11 에 도입된 threadsafe 한 atomic을 적용.
class RefCountBase
{
protected:
	mutable std::atomic<int> refcnt = {0};		// 상수 멤버함수에서도 수정가능한 멤버로 만들어주는 mutable

public:
	void addRef() const 
	{
//		++refcnt;
		refcnt.fetch_add(1, std::memory_order_relaxed);
	}
};

template<typename T>
class RefCount : public RefCountBase
{
public :
//	void release()			// void release(RefCount* this)
	void release() const	// void release(const RefCoutn* this)
	{
		int ret = refcnt.fetch_sub(1, std::memory_order_acq_rel);// refcnt 에서 1을 빼고, 이전값을 return
		if (refcnt == 1)
			delete static_cast<const T*>(this);
	}
};

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

template<typename T>
class AutoPtr
{
	T* obj;
public:
	explicit AutoPtr(T* p = nullptr) : obj(p) { if (obj) obj->addRef(); }
	AutoPtr(const AutoPtr<T*>& ap) : obj(ap.obj) { if (obj) obj->addRef(); }
	~AutoPtr() { if (obj) obj->release(); }
};


int main()
{
	AutoPtr<Truck> p1(new Truck);
	AutoPtr<Truck> p2 = p1;

	// 매번 생성시 addref 필요없어질시 relase 를 부르는것이 비효율적이다.
	// pointer가 객체라면, 생성자/ 복사생성자에서 addref / 소멸자에서 release를 해줄 수있다.
	Truck* p3 = new Truck;
	p3->addRef();
	Truck* p4 = p3;
	p4->addRef();

	p4->release();
	p3->release();
}

 

  • std::shared_ptr 의 단점
    • 참조계수를 관리하는 객체가 2개 이상 생성될 수 있는 위험이 있다.
    • 객체안에 참조계수(관리객체)가 포함되는 것이 안전하다(std::make_shared)

shared_ptr의 위험성

#include<iostream>
#include<atomic>
#include<memory>

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);
}