Smart pointers

2022. 4. 21. 20:04Programming/JAVA, C++, Go, Rust

    목차
반응형

 

raw pointer의 단점



1) pointing 대상이 객체인지 array인지 알수 없음
2) 사용 후 소멸해야 하는지 아닌지 알 수 없음
3) delete를 사용해야 하는지 다른 방법을 사용해야 하는지 알 수 없음
4) delete 인지 delete[]인지 알 수 없음
5) 삭제를 한번만 했는지 보장하기 힘듦
6) dnagling pointer에 대해 처리 방법이 없음

 

 

C++11에는 4가지 type의 smart pointer

1) std::auto_ptr   : C++98 용
auto_ptr은 move 시 null pointer로 set 됨
auto_ptr은 container에서 사용 불가
2) std::unique_ptr
unique_ptr은 move semantics 가능
3) std::shared_ptr
4) std::weak_ptr

 

 

unique_ptr



1) a small, move-only smart pointer
exclusive-ownership에 맞는 smart pointer임
copy가 불가능하며, move만 지원

2) custom deleter가 지정될 수 있음
custom deleter 지정 가능

auto delInvmt = [](Investment* pInvestment) // custom
{                                           // deleter
    makeLogEntry(pInvestment);              // (a lambda
    delete pInvestment;                     // expression)
};

std::unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt);
pInv.reset(new Investment);

3) unique_ptr을 shared_ptr로 전환 가능

std::shared_ptr<Investment> sp = makeInvestment(arguments);

shared_ptr에서는 unique_ptr을 얻을 수 없음


4) unique_ptr은 raw pointer에 성능과 차이가 거의 없음


shared_ptr

1) raw pointer의 2배 크기
referencing mechanism을 처리하기 때문

2) reference count 용 memory는 동작 할당됨
reference count는 객체에 associated
reference count는 동적으로 할당되는 control block에 저장됨

3) reference counting은 atomic 이어야 함


control block의 생성 시점

1) make_shared 시 생성
make_shared는 custom deleter 지정 불가능
아래의 방법으로만 지정 가능
std::shared_ptr<Widget> spw1(new Widget, loggingDel);

2) shared_ptr에 raw pointer 대입 시 생성
단, unique_ptr과 마찬가지로 raw pointer에서의 생성은 피해야함
3) unique_ptr 혹은 auto_ptr 로부터 shared_ptr이 생성될 때

이미 control block을 지닌 객체로부터 shared_ptr 생성시는 control block을 생성하지 않음

raw pointer를 지정해서 생성하는 것은 지양
아래와 같은 코딩이 가능하기 때문임

auto pw = new Widget;
std::shared_ptr<Widget> spw1(pw, loggingDel); // create a control block
std::shared_ptr<Widget> spw2(pw, loggingDel); // create a control block

-> 2회 제거 시도 됨

단점
1) array에 사용할 수 없음
shared_ptr<T[]> 가 없음
unique_ptr<T[]> 는 있으나 사용을 권장하지 않음 대신 array, list등 사용 권장
2) Control BLock의 cost
unique_ptr의 보통 2배 size를 heap에 할당

 

weak_ptr

사용 경우
1) 값을 임시로 caching 하던가 혹은 observing만 하는 경우
 - 지니고 있다고 사용 시 unique_ptr로 전환 후 사용
2) circular referencing 발생 시
shared_ptr 끼리의 상호 참조는 서로 reference count를 지니기에 서로 소멸 시키지 않음 -> leak 야기
한쪽을 weak_ptr로 바꿔서 shared_ptr 소멸 시 weak_ptr은 dangle하게 만듦
(weak_ptr은 dangle 상태를 감지할 수 있음 - 자신의 dereference 상태를 감지)

 

감지 방법

	auto spw = std::make_shared<Widget>();
	std::weak_ptr<Widget> wpw(spw);
	spw = nullptr;
	if (wpw.expired())  <- expired

 

weak_ptr에서의 shared_ptr 생성 방법

 

std::shared_ptr<Widget> spw1 = wpw.lock();

단, shared_ptr 생성 시점에서 weak_ptr이 expired 면, shared_ptr도 null

 

 

make_unique와 make_shared 사용을 선호해야 함

 

make의 장점

    1) 코드 중복 제거
        std::shared_ptr<Widget> spw2(new Widget); <- 중복으로 Widget 지정
        code size 줄임
    2) 예외에 대한 안정성 확보
        processWidget(std::shared_ptr<Widget>(new Widget), computePriority());
        
        new Widget -> shared_ptr -> computePriority 실행
        이 순서가 변경되면, (compiler에 따라) 
        new Widget -> computePriority -> shared_ptr
         -> computePriority에서 exception 발생

        아래와 같이 코딩하면 자원 누수 없음
        processWidget(std::make_shared<Widget>(), computePriority());

    3) control block 통합 객체
        CBLK와 Widget 객체가 한번에 한 곳의 위치에 큰 덩어리로 메모리 할당 됨
        크기 및 속도상에 장점이 있음

 

 

make의 단점


    1) custom deleter 지정 불가 
        custom deleter를 지정하려면 shared_ptr/unique_ptr을 new로 생성해야 함

        아래와 같이 코딩해야 함
        std::shared_ptr<Widget> spw(new Widget, cusDel);
        processWidget(spw, computePriority());

    2) 초기화 리스트 미지원
        auto upv = std::make_unique<std::vector<int>>(10, 20);
        -> 값들이 모두 20인 열 개짜리 vector 한개를 생성
           20, 20, 20, 20, 20, 20, 20, 20, 20, 20
        
        의도한 것은 {10, 20}인데 다르게 생성됨

 

 

weak_ptr의 동작 차이

    1) make로 생성한 경우
        CBLK와 객체가 동일 관리되어 weak ptr의 reference가 0이 될때까지 객체도 해제 안 됨
    2) new로 생성한 경우
        CBLK와 객체가 별도 관리되어 weak ptr의 ref. cnt가 1이상이어도 shared_ptr의 ref cnt가 0이면 객체 소멸됨 (weak_ptr은 dangling 됨)

 

pimpl

pImpl은 생성/해제의 miss를 예방하기 위해 raw pointer보다는 smart pointer를 사용

pimpl의 장점은 haeder를 사용하는 client의 recompile 제거

pimpl 역시 move를 할 수 있으며, move를 하려면 아래의 move operation들을 선언해야 함

 

pimpl에 shared_ptr?

exclusive ownership에는 unique_ptr이 맞음

가능하나 더 heavy

 

 

Lambda에서 universal reference의 perfect forwarding 방법

	auto f = [](auto&& param) {
		return func(normalize(std::forward<decltype(param)>(param)));
	};

 

capture

capture는 기본적으로 local variable에 대해서만 가능함

지역 변수의 scope out 시 잘못된 참조 가능

 

 

 

반응형

'Programming > JAVA, C++, Go, Rust' 카테고리의 다른 글

C++ file path (파일 경로) 획득 방법  (0) 2022.10.04
task-based programming  (0) 2022.04.21
Effective Modern C++  (0) 2022.04.21
Universal reference  (0) 2022.04.16
make_shared, make_unique의 장점  (0) 2022.04.16