unique_ptr,shared_ptr 클래스

xx.xx·2024년 1월 28일
0

c++

목록 보기
5/8

‘unique_ptr’ 클래스

class A
{
    int* data;

public:
    A()
    {
        data = new int[100];
        std::cout << "자원을 획득함!" <<std::endl;
    }

    ~A()
    {
        std::cout << "소멸자 호출!" << std::endl;
        delete[] data;
    }
};

void do_something() { A* pa = new A(); }

int main()
{
    do_something();
}
  • 생성자만 호출하고, 소멸자는 호출되지 않음
  • unique_ptr는 포인터를 대신하는 객체로, 영역을 벗어나면 자동으로 소멸자가 호출되고 할당된 메모리 삭제
  • // 그 이유는 까먹고 delete pa; 를 하지 않았기 때문
Data* data = new Data();
Data* data2 = data;

// data : 사용이 끝났으니 소멸 해야지
delete data;

// data2 : 나도 사용 다 했으니 소멸해야지
delete data2;
  • data1, data2가 동시에 한 객체를 가리키고 delete data를 통해 객체를 소멸할 겨우 double free 로 프로그램 종료
  • 객체의 소유권이 명확하지 않기 때문
    • 어떤 포인터에 객체 유일한 소유권을 부여하여, 이 포인터 외에는 객체를 소멸시킬 수 없도록
    • 특정 객체에 유일한 소유권을 부여하는 포인터 객체 unique_ptr

unique_ptr의 특징

  • 복사 생성자와 할당 연산자의 삭제: unique_ptr 는 자원 소유의 독점적인 책임을 갖는 스마트 포인터로, 복사 생성자와 할당 연산자가 삭제되어 있어 복사를 방지하고 이동 의미구조 강조
 std::unique_ptr<int> up1(new int);
 std::unique_ptr<int> up2(up1);      // 컴파일 에러

복사 생성자와 할당 연산자가 삭제되어 있어 독점 포인터 복사 시 컴파일 에러 발생

자원의 독점적인 소유를 보장하기 위해 복사를 허용하지 않음

  • 이동 의미구조를 이용한 생성과 초기화:
    unique_ptr<int> cp(unique_ptr<int>(new int));

이동 의미구조를 활용하여 독점 포인터를 생성하고 초기화합니다.

unique_ptr<int>(new int)는 rvalue로 취급되며, 이를 이동 생성자를 이용해 새로운 독점 포인터에 전달

이렇게 하면 소유권이 이전되고, 복사 대신 효율적인 이동이 이루어짐

  • 이동 의미구조의 편의기능:
class unique_ptr                 // 부분적으로 보여지는 인터페이스
{
    public:
        unique_ptr(unique_ptr &&other); // 여기에서 rvalue를 묶는다.
    private:
        unique_ptr(const unique_ptr &other);
};

unique_ptr 클래스는 이동 의미구조를 사용하기 위해 이동 생성자 제공

이동 생성자는 rvalue 참조를 매개변수로 받아와서 소유권을 이전하는 역할

복사 생성자는 삭제되어 있어 복사를 막고, 이동 생성자를 통해 효율적인 자원 이전

/*

  • unique_ptr를 할당할 때 이동 의미구조가 사용된다.
std::unique_ptr<int> up1(new int);
std::unique_ptr<int> up2(up1);      // 컴파일 에러
  • 두 번째 정의는 컴파일에 실패.
  • unique_ptr의 복사 생성자가 private 멤버이기 때문
  • unique_ptr는 rvalue 참조로부터 *할당하고 초기화하는 편의 기능을 제공*
    class unique_ptr                 // 부분적으로 보여지는 인터페이스
    {
        public:
            unique_ptr(unique_ptr &&other); // 여기에서 rvalue를 묶는다.
        private:
            unique_ptr(const unique_ptr &other);
    };
    다음 예제는 이동 의미구조를 사용한다. 그래서 올바르게 컴파일된다.
    unique_ptr<int> cp(unique_ptr<int>(new int));
  • unique_ptr는 동적으로 사용 가능한 메모리만 가리켜야 함
  • 동적으로 할당된 메모리만 지울 수 있기 때문
  • 동적으로 할당된 똑같은 메모리 블록을 여러 개의 unique_ptr가 가리키면 안 됨
  • unique_ptr의 인터페이스는 이런 일이 일어나지 않도록 설계됨
  • unique_ptr 객체가 영역을 벗어나면 자신이 가리키는 메모리를 삭제하고 할당된 그 메모리를 가리키는 다른 객체들도 즉시 날 포인터로 바꾼다.

*/

unique_ptr<int> ptr1(new int(42));
unique_ptr<int> ptr2(std::move(ptr1));

ptr1은 더 이상 소유권을 갖지 않고, ptr2ptr1의 소유권을 얻게 됨

동적으로 할당된 자원을 효과적으로 이동하면서 소유 권한을 전환 시 유용

std::unique_ptr를 사용하여 파생 클래스(Derived) 객체를 기본 클래스(Base) 포인터로 독점적으로 소유하는 방법

다형성을 사용하지 않고, Derived 클래스의 소멸자가 자동으로 호출되는 것 확인 가능

  1. Derived 객체를 unique_ptr에 할당
  2. deleter 함수는 Base 포인터를 받아서 static_cast를 사용하여 Derived 포인터로 형변환한 후 delete 연산자를 통해 메모리를 해제
  3. unique_ptr가 소유한 포인터를 get 함수로 가져와서 static_cast를 사용하여 다시 Derived 포인터로 형변환한 후, process 멤버 함수를 호출합니다. 이 때, process 함수는 Derived 클래스에 존재하는 함수로 정상적으로 호출됩니다.
  4. 자동으로 소멸자 호출:unique_ptr는 소유한 객체가 범위를 벗어나면 자동으로 소멸자를 호출합니다. 이 예제에서는 main 함수가 종료될 때 unique_ptr 객체 bp가 범위를 벗어나면서 ~Derived가 호출되어 Derived 객체의 메모리가 정상적으로 해제

Derived 클래스가 Base로부터 파생될 때, 새로 할당된 Derived 클래스 객체는 unique_ptr<Base>에 할당할 수 있다. 

Base에 대하여 가상 소멸자를 정의할 필요가 없다. 

unique_ptr의 정의에 deleter 함수의 주소를 제공하기만 하면 독점 포인터 객체는 Base * 포인터를 돌려준다.

이 포인터를 그냥 정적으로 Derived 유형으로 변환하면 되며, Derived 클래스의 소멸자도 자동으로 호출된다.

class Base
    { ... };
    class Derived: public Base
    {
        ...
        public:
            // Derived에 void process() 멤버가 있다고 가정한다.
            static void deleter(Base *bp);
    };
    void Derived::deleter(Base *bp)
    {
        delete static_cast<Derived *>(bp);
    }
    int main()
    {
        unique_ptr<Base, void (*)(Base *)> bp(new Derived, &Derived::deleter);
        static_cast<Derived *>(bp.get())->process(); // OK!

    } // 여기에서 ~Derived가 호출된다. 다형성을 요구하지 않는다.

unique_ptr클래스에서 제공하는 여러 멤버 함수로 포인터 자체에 접근하거나 unique_ptr가 또다른 메모리 블록을 가리키도록 만들 수 있다.

‘unique_ptr’ 객체 정의하기

unique_ptr 정의하는 세가지 방법

  • 기본 생성자는 단순하게 unique_ptr를 생성한다.
  • 특별한 메모리 블록을 가리키지 않는다. 포인터는 0으로 초기화된다.
unique_ptr<type> identifier;
  • 이동 생성자는 unique_ptr를 초기화한다.
  • 이동 생성자를 사용하고 나면 unique_ptr 인자는 더 이상 동적으로 할당된 메모리를 가리키지 않는다.
  • 그리고 포인터 데이터 멤버는 0-포인터로 바뀐다.
    unique_ptr<type> identifier(another unique_ptr for type);
  • 가장 많이 사용되는 형태는 동적으로 할당된 메모리 블록으로 unique_ptr를 초기화하는 것
  • 이 메모리는 객체의 생성자에 건네어진다.
  • 선택적으로 소멸자(deleter)를 제공할 수 있음
  • unique_ptr의 포인터를 인자로 받는 자유 함수를 또는 함수객체를 소멸자로 건넬 수 있다.
  • 동적으로 할당된 메모리를 공용 풀에 반납하도록 되어 있다.
  • 포인터가 0이면 아무 일도 하지 않는다.
    unique_ptr<type> identifier (new-expression [, deleter]);

평범한 `unique_ptr' 만들기

unique_ptr의 기본 생성자는 특정한 메모리 블록을 가리키지 않는다.

    unique_ptr<type> identifier;

unique_ptr가 제어하는 포인터는 0으로 초기화된다.

unique_ptr객체 자체는 포인터가 아니지만 그 값은 0 과 비교할 수 있다

    unique_ptr<int> ip;

    if (!ip)
        cout << "0-pointer with a unique_ptr object\n";

또다른 `unique_ptr' 이동하기

unique_ptr는 유형이 같으면 rvalue 참조를 사용하여 초기화할 수 있다.

    unique_ptr<type> identifier(other unique_ptr object);

다음 예제에 이동 생성자가 사용된다.

    void mover(unique_ptr<string> &&param)
    {
        unique_ptr<string> tmp(move(param));
    }

비슷하게 할당 연산자를 사용할 수 있다.

unique_ptr객체는 유형이 같은 임시 unique_ptr객체에 할당할 수 있다.

역시 이동-의미구조가 사용된다.

    #include <iostream>
    #include <memory>
    #include <string>

    using namespace std;

    int main()
    {
        unique_ptr<string> hello1(new string("Hello world"));
        unique_ptr<string> hello2(move(hello1));
        unique_ptr<string> hello3;

        hello3 = move(hello2);
        cout << // *hello1 << /\n' <<   // 세그먼트 폴트 에러가 일어날 것임
                // *hello2 << '\n' <<   // 마찬가지
                *hello3 << '\n';
    }
    // 출력:    Hello world
  • hello1은 동적으로 할당된 string을 가리키는 포인터로 초기화
  • unique_ptr hello2 는 이동 생성자를 사용하여 hello1이 제어하는 포인터를 획득
  • hello1을 0-포인터로 바꿈
  • hello3이 기본 unique_ptr<string>로 정의된다.
  • 그러나 다음 이동-할당을 사용하여 그의 값을 hello2으로부터 획득한다. 결과적으로 역시 0-포인터로 바뀐다.

hello1이나 hello2가 cout으로 삽입되면 세그먼트 폴트 에러가 일어날 것이다.

0-포인터를 역참조했기 때문이다. 결국, hello3만 실제로 원래 할당된 string을 가리킨다.

새로 할당된 객체를 가르키기

주로 동적으로 할당된 메모리를 가리키는 포인터를 사용하여 초기화됨

    unique_ptr<type [, deleter_type]> identifier(new-expression
            [, deleter = deleter_type()]);

두 번째 deleter(_type) 인자는 선택적

할당된 메모리를 반납하는 자유 함수나 함수 객체를 참조

이중 포인터가 할당되어 있고 내포된 포인터마다 방문하여 할당된 메모리를 파괴하는 소멸자

다음 예제는 문자열 객체를 가리키는 독점 포인터를 초기화

    unique_ptr<string> strPtr(new string("Hello world"));

생성자에 건네어진 인자는 operator new 가 돌려준 포인터이다.

유형에 그 포인터가 언급되지 않는 것을 눈여겨보라.

unique_ptr 의 생성에 사용된 유형은 new 표현식에 사용된 유형과 똑같다.

명시적으로 소멸자를 정의하여 어떻게 동적으로 할당된 문자열을 가리키는 포인터 배열 삭제

    struct Deleter
    {
        size_t d_size;
        Deleter(size_t size = 0) : d_size(size){}
        void operator()(string **ptr) const
        {
            for (size_t idx = 0; idx < d_size; ++idx)
                delete ptr[idx];
            delete[] ptr;
        }
    };
    int main()
    {
        unique_ptr<string *, Deleter> sp2(new string *[10], Deleter(10));

        Deleter &obj = sp2.get_deleter();
    }

unique_ptr 를 사용하면 new 표현식에 의해 할당된 실체의 멤버 함수에 도달할 수 있다.

unique_ptr 는 마치 동적으로 할당된 객체를 가리키는 평범한 포인터인 것처럼 이런 멤버에 도달할 수 있다.

다음 프로그램은 hello`' 단어 뒤에 C++`' 문자열을 삽입한다.

#include <iostream>
    #include <memory>
    #include <cstring>
    using namespace std;

    int main()
    {
        unique_ptr<string> sp(new string("Hello world"));

        cout << *sp << '\n';
        sp->insert(strlen("Hello "), "C++ ");
        cout << *sp << '\n';
    }
    /*
        출력:
            Hello world
            Hello C++ world
    */

unique_ptr 클래스 연산자와 멤버

unique_ptr는 다음 연산자를 제공한다.

  • unique_ptr<Type> &operator=(unique_ptr<Type> &&tmp):

    이 연산자는 이동 의미구조를 사용하여 rvalue unique_ptr가 가리키는 메모리를 lvalue unique_ptr로 이전

    그래서 rvalue 객체는 자신이 가리키던 메모리를 잃어버리고 0-포인터로 바뀜

    기존의 unique_ptr를 또다른 unique_ptr에 할당할 수 있다. std::move를 사용하여 먼저 rvalue 참조로 변환

    unique_ptr<int> ip1(new int);
    unique_ptr<int> ip2;
    
    ip2 = std::move(ip1);
  • Type &operator*():

    이 연산자는 독점 포인터를 통하여 접근할 수 있는 정보를 참조로 돌려준다.

    마치 평범한 포인터 역참조(dereference) 연산자처럼 행위한다.

  • Type *operator->():

    이 연산자는 독점 포인터를 통하여 접근할 수 있는 정보를 포인터로 돌려준다.

    이 연산자로 독점 포인터를 통하여 객체에 접근해 멤버를 선택할 수 있다.

    unique_ptr<string> sp(new string("hello"));
    cout << sp->c_str();

unique_ptr는 다음의 멤버 함수를 제공한다.

  • Type *get():

    독점 포인터가 통제하는 정보를 포인터로 돌려준다. 마치 operator->처럼 행위한다.

    반환된 포인터를 조사할 수 있다. 만약 0이면 독점 포인터는 아무 메모리도 가리키지 않는다.

  • Deleter &unique_ptr<Type>::get_deleter():

    독점 포인터가 사용하는 소멸자를 참조로 돌려준다.

  • Type *release():

    독점 포인터를 통하여 접근할 수 있는 정보를 포인터로 돌려준다. 동시에 객체 자체는 0-포인터가 된다.

  • void reset(Type *):

    독점 포인터가 통제하는 동적으로 할당된 메모리를 공용 풀에 반납한다. 그 때부터 이 멤버 함수에 건넨 인자가 가리키는 메모리는 독점 포인터가 통제한다. 이 멤버 함수는 인자 없이 호출할 수도 있다. 그러면 독점 포인터는 0-포인터로 바뀐다. 이 멤버 함수를 사용하면 동적으로 할당된 새 메모리 블록을 독점 포인터에 할당할 수 있다.

배열에 `unique_ptr' 객체 사용하기

동적으로 할당된 배열에 다음 구문을 사용할 수 있다.

  • 인덱스 표기법을 ([]) 사용하여 동적으로 할당된 배열을 스마트 포인터가 통제하도록 지정한다.
    unique_ptr<int[]> intArr(new int[3]);
  • 인덱스 연산자를 사용하면 배열의 원소에 접근할 수 있다.
    intArr[2] = intArr[0];

이 경우 스마트 포인터의 소멸자는 delete가 아니라 delete[]를 호출

‘shared_ptr’ 클래스

공유 포인터를 정의하는 방법 네 가지

  • 기본 생성자는 단순한 공유 포인터를 만든다.
  • 특별히 메모리 블록을 가리키지 않는다. 포인터는 0 (영)으로 초기화
    shared_ptr<type> identifier;
  • 복사 생성자는 두 객체 모두 기존의 객체가 가리키는 메모리를 공유하도록 공유 포인터를 초기화
  • 복사 생성자는 공유 포인터의 참조 횟수도 증가
    shared_ptr<string> org(new string("hi there"));
    shared_ptr<string> copy(org);   // 참조 횟수는 이제 2이다.
  • 이동 생성자는 공유 포인터를 임시 공유 포인터의 참조 횟수와 포인터로 초기화한다.
  • 임시 공유 포인터는 0-포인터로 바뀐다.
  • 기존의 공유 포인터는 자신의 데이터를 새로 정의된 공유 포인터로 옮길 수도 있다.
  • 역시 기존의 공유 포인터를 0-포인터로 바꾼다.
  • 다음 예제는 임시의 익명 공유 포인터를 생성한다. 다음 grabber를 생성한다.
  • grabber의 생성자는 익명의 임시 객체를 받으므로 컴파일러는 공유 포인터의 이동 생성자를 사용한다.
    shared_ptr<string> grabber(shared_ptr<string>(new string("hi there")));
  • 다음 형태는 공유 포인터를 동적으로 할당된 메모리 블록으로 초기화한다.
  • 동적으로 할당된 메모리 블록은 객체의 생성자에 건네진다. 선택적으로 소멸자(deleter)를 제공할 수 있다.
  • 공유 포인터를 인자로 받는 자유 함수나 함수객체를 소멸자에 건넬 수 있다.
  • 동적으로 할당된 메모리를 공용 풀에 돌려주도록 되어 있다. 포인터가 0이면 아무 일도 하지 않는다.
    shared_ptr<type> identifier (new-expression [, deleter]);

    평범한 `shared_ptr' 만들기

    기본 생성자는 공유 포인터를 단순하게 정의한다. 특정 메모리 블록을 가리키지 않는다.
        shared_ptr<type> identifier;

    새로 할당된 객체를 공유 포인터로 가리키기

    공유 포인터는 주로 동적으로 할당된 메모리 블록으로 초기화된다.
        shared_ptr<type> identifier(new-expression [, deleter]);
    
    두 번째 인자(deleter)는 선택적 할당된 메모리를 파괴하는 자유 함수나 함수 객체를 참조 이중 포인터가 할당되고 내포된 포인터마다 방문하면서 할당된 메모리를 파괴하는 소멸자 독점 포인터가 마주하는 상황에 사용 다음은 문자열 객체를 가리키는 공유 포인터를 초기화하는 예이다.
        shared_ptr<string> strPtr(new string("Hello world"));
    생성자에 건네진 인자는 operator new가 반환하는 포인터이다. 공유 포인터 생성에 사용된 유형은 new 표현식에 사용된 유형과 똑같다. 두 개의 공유 포인터가 실제로 정보를 공유하는 것을 보여준다. 한쪽에서 정보를 변경하면 변경된 정보가 다른 쪽에 보인다.
        int main()
        {
            shared_ptr<string> sp(new string("Hello world"));
            shared_ptr<string> sp2(sp);
    
            sp->insert(strlen("Hello "), "C++ ");
            cout << *sp << '\n' <<
                    *sp2 << '\n';
        }
        /*
            출력:
                Hello C++ world
                Hello C++ world
        */

    공유 포인터 형변환 하기

공유 포인터와 조합하여 표준 C++ 스타일로 유형을 변환할 때 주의할 점

  struct Base
    {};
    struct Derived: public Base
    {};

새로 할당된 Derived 클래스 실체를 저장하기 위하여 shared_ptr<Base>를 정의하면 Base * 유형이 반환된다.

이 반환 유형은 단독 포인터처럼 static_cast를 사용하여 Derived *으로 형변환할 수도 있다.

다형성은 요구하지 않는다. 공유 포인터를 재초기화하거나 공유 포인터가 영역을 벗어나더라도 슬라이싱(복사손실)은 일어나지 않는다.

Derived 객체는 Base 객체이기도 하므로 굳이 형변환하지 않아도 Derived를 가리키는 포인터는 Base를 가리키는 포인터로 간주해도 된다.

그러나 static_cast를 사용하면 Derived *를 Base *로 번역하도록 강제할 수 있다.

Derived d;
    static_cast<Base *>(&d);

그렇지만 Derived 객체를 가리키는 공유 포인터의 get 멤버를 사용하여 공유 포인터를 Base로 변환할 때 평범한 static_cast는 사용할 수 없다.

다음 코드는 결국 동적으로 할당된 Base 객체를 두 번 파괴하려고 시도하게 될 것이다.

    shared_ptr<Derived> sd(new Derived);
    shared_ptr<Base> sb(static_cast<Base *>(sd.get()));

sd 와 sb 는 같은 객체를 가리키기 때문에 sb 와 sd 가 영역을 벗어나면 ~Base 소멸자가 두 번 호출된다.

그 때문에 프로그램은 이르게 종료하게 된다.두 번 해제하는 에러가 일어나기 때문이다.

이 에러는 공유 포인터와 함께 사용되도록 특별히 설계된 형변환을 사용하면 방지할 수 있다.

이 형변환은 특정화된 생성자를 사용한다.

생성된 공유 포인터는 메모리를 가리키지만 소유권을 (즉, 참조 횟수를) 기존의 공유 포인터와 공유한다.

  • std::static_pointer_cast<Base>(std::shared_ptr<Derived> ptr):

    Base 클래스를 가리키는 공유 포인터를 돌려준다. 반환된 공유 포인터는 shared_ptr<Derived> ptr가 참조하는 Derived 클래스에서 바탕 클래스 부분을 참조한다.

    shared_ptr<Derived> dp(new Derived());
    shared_ptr<Base> bp = static_pointer_cast<Base>(dp);
  • std::const_pointer_cast<Class>(std::shared_ptr<Class const> ptr):

    Class 클래스를 가리키는 공유 포인터를 돌려준다. 반환된 공유 포인터는 가변 Class 객체를 참조한다. 반면에 ptr 인자는 Class const 객체를 참조한다.

    shared_ptr<Derived const> cp(new Derived());
    shared_ptr<Derived> ncp = const_pointer_cast<Derived>(cp);
  • std::dynamic_pointer_cast<Derived>(std::shared_ptr<Base> ptr):

    Derived 클래스 객체를 가리키는 공유 포인터를 돌려준다. Base 클래스는 적어도 하나의 가상 멤버 함수를 가져야 하며, Base를 상속받은 Derived 클래스는 Base의 가상 멤버를 재정의할 수도 있다. Base *으로부터 Derived *으로 동적 변환에 성공하면 반환된 공유 포인터는 Derived 클래스 객체를 참조한다. 동적 형변환에 실패하면 공유 포인터의 get 멤버는 0을 돌려준다. 예를 들어 (Derived와 Derived2를 Base로부터 상속받았다고 가정함):

    shared_ptr<Base> bp(new Derived());
    cout << dynamic_pointer_cast<Derived>(bp).get() << ' ' <<
            dynamic_pointer_cast<Derived2>(bp).get() << '\n';

    첫 번째 get은 0-아닌 포인터를 돌려준다. 두 번째 get은 0을 돌려준다.

    배열에 `shared_ptr' 객체 사용하기

    독점 포인터와 다르게 공유 포인터는 동적으로 할당된 배열 객체를 다루기 위한 특정화가 존재하지 않는다. 그러나 독점 포인터처럼 공유 포인터로 배열을 참조하면 역참조 연산자는 의미가 없어진다. 대신에 공유 포인터는 인덱스 연산자를 이용할 수 있다. 그런 편의기능을 제공하는 shared_array 클래스를 만드는 것은 어렵지 않다. shared_ptr 클래스로부터 상속받아 shared_array 클래스 템플릿을 만들고 거기에 소멸자만 주면 배열과 그의 원소들을 파괴해 준다. 게다가 인덱스 연산자를 정의하면 선택적으로 delete를 사용하여 역참조 연산자를 선언할 수 있다. 다음은 어떻게 shared_array를 정의하고 사용하는지 보여준다.
      struct X
        {
            ~X()
            {
                cout << "destr\n";  // 객체의 소멸을 보여준다.
            }
        };
        template <typename Type>
        class shared_array: public shared_ptr<Type>
        {
            struct Deleter          // 소멸자는 포인터를 받는다.
            {                       // 그리고 delete[]를 호출한다.
               void operator()(Type* ptr)
               {
                  delete[] ptr;
               }
            };
            public:
                shared_array(Type *p)           // 다른 생성자는
                :                               // 여기에 없다.
                    shared_ptr<Type>(p, Deleter())
                {}
                Type &operator[](size_t idx)    // 인덱스 연산자
                {
                    return shared_ptr<Type>::get()[idx];
                }
                Type const &operator[](size_t idx) const
                {
                    return shared_ptr<Type>::get()[idx];
                }
                Type &operator*() = delete;     // 포인터가 없는 멤버는 제거한다.
                Type const &operator*() const = delete;
                Type *operator->() = delete;
                Type const *operator->() const = delete;
        };
        int main()
        {
            shared_array<X> sp(new X[3]);
            sp[0] = sp[1];

    스마트 포인터 생성: make_shared' 그리고 make_unique'

    정의 시간에 공유 포인터는 새로 할당된 객체를 가리키는 포인터로 초기화된다.
        std::shared_ptr<string> sptr(new std::string("hello world"))
    이 서술문은 메모리를 두 번 **할당한다. 하나는 std::string를 할당하고, 다른 하나는 내부적으로 공유 포인터의 생성자 자체에서 사용 make_shared 템플릿을 사용하면 두 번의 할당을 하나의 할당으로 조합할 수 있음 std::make_shared 함수 템플릿 원형:
        template<typename Type, typename ...Args>
        std::shared_ptr<Type> std::make_shared(Args ...args);
    이 함수 템플릿은 Type 유형의 객체를 할당하고 args를 생성자에 건네며 새로 할당된 Type 객체의 주소로 초기화된 공유 포인터를 돌려준다. 다음은 std::make_shared를 사용하여 위의 sptr 객체를 초기화하는 방법
        auto sptr(std::make_shared<std::string>("hello world"));
    이렇게 초기화하고 나면 std::shared_ptr<std::string> sptr이 정의되고 초기화
      `std::cout << *sptr << '\n';` 같이 쓰일 수 있음
    C++14 표준은 또 std::make_unique을 제공한다. 이를 make_shared처럼 사용할 수 있지만 공유 포인터가 아니라 독점 포인터를 만들어 준다.

0개의 댓글