728x90
반응형

std::unique_ptr은 독점적 소유권 의미론을 재현하는 클래스이다. null이 아닌 std::unique_ptr은 항상 자신이 가리키는 개체를 소유한다. 그에 따라 복사를 허용하지 않으며, 오직 이동만 가능하다. 그리고 raw포인터와 거의 같은 크기를 가지기 때문에 메모리와 CPU 성능이 넉넉하지 않더라도 사용하기에 충분하다.

 

std::unique_ptr객체는 자신이 파괴될 때, 가리키는 자원 또한 함께 파괴된다.

std::unique_ptr객체의 파괴는 delete를 통해 일어나나 커스텀 삭제자를 사용해서 삭제할 수도 있는데 상태가 있는 삭제자나 함수포인터를 사용하면 std::unique_ptr의 크기가 커진다.

auto delInvmt = [](Investment* pInvestment)
                {
                    makeLogEntry(pInvestment);
                    delete pInvestment;
                };
                
template<typename... Ts>
std::unique_ptr<Investment, decltype(delInvmt)>
makeInvsetment(Ts&&... params)
{
    std::unique_ptr<Investment, decltype(delInvmt)>
      pInv(nullptr, delInvmt);
      
    if ( /* Stock 객체를 생성해야 하는 경우 */ )
    {
        pInv.reset(new stock(std::forward<Ts>(params)...));
    }
    else if ( /* Bond 객체를 생성해야 하는 경우 */ )
    {
        pInv.reset(Bond stock(std::forward<Ts>(params)...));
    }
    else if ( /* RealEstate 객체를 생성해야 하는 경우 */ )
    {
        pInv.reset(new RealEstate(std::forward<Ts>(params)...));
    }
    
    return pInv;
}

위의 함수를 사용하는 호출자는 반환받은 객체의 소멸이 오직 한번만 일어난다는 것과 소멸 방식에 대해 신경 쓰지 않아도 된다는 장점이 생긴다.

 

그리고 C++14에서는 반환타입 추론을 허용하므로 위의 코드를 좀 더 간결하고 캡슐화된 방식으로 구현할 수 있다.

template<typename... Ts>
auto makeInvsetment(Ts&&... params)
{
    auto delInvmt = [](Invsetment* pInvestment)
    {
        makeLogEntry(pInvestment);
        delete pInvestment;
    };
    
    std::unique_ptr<Investment, decltype(delInvmt)>
      pInv(nullptr, delInvmt);
      
    if ( /* Stock 객체를 생성해야 하는 경우 */ )
    {
        pInv.reset(new stock(std::forward<Ts>(params)...));
    }
    else if ( /* Bond 객체를 생성해야 하는 경우 */ )
    {
        pInv.reset(Bond stock(std::forward<Ts>(params)...));
    }
    else if ( /* RealEstate 객체를 생성해야 하는 경우 */ )
    {
        pInv.reset(new RealEstate(std::forward<Ts>(params)...));
    }
    
    return pInv;
}

 

그리고 std::unique_ptr의 크기는 raw포인터와 거의 같다고 하였는데, 커스텀 삭제자를 사용하면 달라진다.

일반적으로, 함수 포인터를 삭제자로 지정한 경우에는 std::unique_ptr의 크기가 1워드에서 2 워드로 증가하며 삭제자가 함수 객체일 경우에는 그 함수 객체에 저장된 상태의 크기만큼 증가한다.

상태가 없는 함수 객체(ex.람다식..)의 경우에는 크기 변화가 없으며, 따라서 가능하면 람다식을 선호하는 것이 바람직하다.

// 람다식으로 작성한 경우
auto delInvmt = [](Investment* pInvestment)
                {
                    makeLogEntry(pInvestment);
                    delete pInvestment;
                };
                
template<typename... Ts>
std::unique_ptr<Investment, decltype(delInvmt)>  // 반환 형식은 Investment* 와 같은 크기
makeInvsetment(Ts&&... params);


// 함수 형태로 작성한 경우
void delInvmt2(Investment* pInvestment)
{
    makeLogEntry(pInvestment);
    delete pInvestment;
};

template<typename... Ts>
std::unique_ptr<Investment, void (*)(Investment(*)>  // 반환 형식은 Investment* 에 함수 포인터의 크기를 더한 크기
makeInvsetment(Ts&&... params);

 

마지막으로 팩터리 함수에서 std::unique_ptr을 std::shared_ptr로 손쉽게 변환할 수 있는데, 팩터리 함수는 자신이 돌려준 객체를 호출자가 독점적으로 소유하려 하는지 소유권을 공유하려 하는지 미리 알 수 없다.

팩터리 함수가 std::unique_ptr을 반환한다면 호출자는 가장 효율적인 스마트 포인터를 얻게 되며 상황에 따라 아래처럼 std::shared_ptr로 변환하여 쓸 수도 있다.

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

하지만 반대로 std::shared_ptr을 std::unique_ptr로 변환하는 것은 불가능하다.

 

728x90
반응형