C++ unique_ptr

m._.jooong·2023년 5월 8일
0

C++

목록 보기
23/23
#include <iostream>

/** 문자열 관련 라이브러리*/
#include <string>
/** 스마트 포인터 관련 라이브러리*/
#include <memory>
#include <vector>

/** C++ 표준 라이브러리 사용합니다. */
using namespace std;

/* 정적 할당 테스트 Animal 클래스 정의*/
class Animal
{
public:
    Animal()
    {
        cout << "정적 할당으로 객체 생성한 생성자" << endl;
    }

    ~Animal()
    {
        cout << "정적 할당으로 객체 생성한 소멸자" << endl;
    }
};

/* new 연산자를 이용한 동적할당 테스트 Lion 클래스 정의*/
class Lion
{
public:
    Lion()
    {
        cout << "new 연산자로 동적 객체 생성한 생성자" << endl;

    }

    ~Lion()
    {
        cout << "new 연산자로 동적 객체 생성한 소멸자" << endl;

    }
};

/* 스마트 포인터로 동적 객체 생성하기 Tiger 클래스 정의*/
class Tiger
{
public:
    Tiger()
    {
        cout << "스마트 포인터로 동적으로 객체 생성한 생성자 " << endl;
    }

    ~Tiger()
    {
        cout << "스마트 포인터로 동적으로 객체 생성한 소멸자 " << endl;
    }

};

/* cat 클래스 정의하기.*/
class Cat
{
private:
    int* m_Food;
public:
    Cat()
    {
        /* 생성자에서 사이즈가 100000인 배열을 동적 할당으로 생성하기*/
        m_Food = new int[100000];
        cout << " Cat 생성자 " << endl;
    }

    ~Cat()
    {
        /* 소멸자는 객체가 메모리에서 해제될 때 할 일들을 지정해 준다.
        소멸자에서 사이즈가 100000인 배열을 delete 연산자로 객체 해제 해 주지 않으면
        4바이트 * 100000의 메모리 누수가 발생한다.
        */
        //delete m_Food;

        cout << " Cat 소멸자 " << endl;
    }
};

int main()
{
    /**
   구글 검색 없이 자연스럽게 써야 하는 문법적 요소
   1. 포인터 Pointer
   2. 참조자 Reference
   3. 상수 Constant
   4. 동적 할당 Dynamic Memory Allocation
   5. 스마트 포인터
       unique_ptr : 객체의 유일한 소유권을 가지는 스마트 포인터
       shared_ptr : 객체간의 공유가 가능한 스마트 포인터
       weak_ptr : shared_ptr의 순환 참조 문제를 해결한 약한 참조의 스마트 포인터
   */

   /**
   C++ 에서 객체 생성 방법

   1. 정적 할당 : 미리 할당을 합니다. 쓰지 않더라도 미리 할당을 합니다.
       비효율적인 부분이 발생할 수 있습니다. 하지만 내가 메모리 관리를 할 필요가 없습니다.

   2. 동적 할당 : 런타임에서 메모리 할당을 합니다. 효율적으로 필요한 부분에서 메모리 할당을 합니다.
       까다로운 부분이 있습니다. 하지만 효율을 위해서 동적으로 객체 생성을 해야 합니다.
       A. new 연산자로 객체 생성을 합니다. delete 연산자로 객체 해제를 해 주어야 합니다.
           만일 delete 연산자로 객체 해제를 해주지 않으면 메모리 누수가 발생합니다.
           계속 메모리에 쓰레기가 쌓여서 프로그램이 crash될 수도 있습니다.
           메모리 누수 : 내가 접근할 수 없는, 내가 지울 수 없는 쓰레기 객체들이 메모리에 계속 쌓입니다.
               나중에는 앱이나 게임이 강제로 종료될 수도 있습니다.
               이 부분 때문에 C++에서는 경험이 많이 필요했습니다.
       B. 2011년도에 스마트 포인터를 지원하면서 메모리 관리가 너무 편해졌습니다.
           내가 객체 해제를 해 줄 필요가 없습니다.
           메모리 누수 발생을 원천적으로 예방할 수 있습니다.
   */

   /* 정적 할당 테스트 Animal 클래스 정의*/
    Animal animal1;
    cout << endl;

    /* new 연산자를 이용한 동적할당 테스트 Lion 클래스 정의*/
    Lion* lion1 = new Lion();
    cout << endl;

    /** 더이상 접근할 필요가 없을 때는 delete 연산자로 메모리에서 객체 해제를 해주어야 합니다. */
    delete lion1;
    cout << endl;

    /* 스마트 포인터로 동적 객체 생성하기 Tiger 클래스 정의*/
    unique_ptr<Tiger> uniquePtr1 = make_unique<Tiger>();
    cout << endl;
    
    /* 
    delete 연산자 사용의 복잡성과 메모리 누수 예방을 위해 스마트 포인터가 생겼다.

    1. unique_Ptr : C++ 11 (2011년)에 추가 되었다 객체의 유일한 소유권을 가지는 스마트 포인터
    2. shared_Ptr : C++ 11 (2011년)에 추가 되었다 객체간의 공유가 가능한 스마트 포인터
    3. weak_ptr   : C++ 11 (2011년)에 추가 되었다 shared_ptr의 순환 참조 문제를 해결한
                    약한 참조의 스마트 포인터
    4. auto_ptr : C++ 17년에 없어졌음.

    C언어 malloc(말록, Memory Allocation)함수, free() 함수로 메모리 관리를 했다.
    C++ 에서는 new연산자 delete 연산자로 메모리 관리를 하지만 어렵다.
    메모리 누수 체크를 하는 많은 툴이 있지만 복잡하다

    스마트 포인터를 지원하면서 메모리 관리가 쉬워졌다.
    */

    /* 
    자원 관리 중요성

    메모리를 할당만 하고 해제를 하지않는다면 결국 메모리 부족으로 프로그램이 crash될 수 있다
    C++이후 나온 많은 언어들 대부분 가비지 컬렉션(자원 청소)를 지원한다.
    가비지 컬렉션ㅇ이 1초에 몇십번 호출되어서 메모리에 접근이 안되는 쓰레기 자원들을 정리한다.

    따라서 프로그래머들이 자원을 해제하는 일에 대해 신경쓰지 않아도 되는 장점이 있지만
    너무 의존하게 되면 게임이 느려질 수 있다. 코드 최적화도 중요하다.
    */


    /* cat 클래스 정의하기.*/
    /*
    생성자 함수에서 엄청 큰 사이즈의 큰 배열을 정의하고 있다.
    Cat()
    {
        m_Food = new int[100000];
        cout << " Cat 생성자 " << endl;
    }

    객체가 생성만 되고, 생성된 객체가 해제되지 않았다.
    메모리 누수가 발생하는데, 얼마만큼의 메모리 누수가 발생할까

    m_Food = new int[100000];
    int타입은 4바이트이다. 4바이트 * 100000 = 400000Byte

    Cat이라는 객체가 생성될 때마다 400000byte의 메모리 누수가 발생한다.

    heap 메모리 공간 어딘가에 Cat 객체가 남아있지만, 그 주소값을 가지고 있는 포인터는 메모리 상에
    남아있지 않다. Cat객체는 영원히 해제되지 않은채로 heap 메모리 공간에서 자리만 차지한다'

    delete 연산자로 메모리에서 객체 해제해 주면 되지 않겠냐 하지만 프로그램의 크기가 커지면
    해제하는 위치가 복잡할 수 있다. 놓치기 쉽다.
    */

    Cat* cat1 = new Cat();
    cout << endl;

    delete cat1;
    cout << endl;

    /*
    C++ 에서 메모리 관리를 잘못했을 때 2가지 문제점이 발생할 수 있다.

    1. 메모리를 사용한 후에 해제하지 않는 경우. 메모리 누수가 발생한다.
    2. 이미 해제된 메모리를 다시 참조하는 경우. 이미 해제한 메모리는 nullptr이 된다.
        nullptr인 객체에 접근하면 에러가 발생한다
    */

    /* 이미 해제된 메모리를 다시 참조했을 때... */
    Cat* cat2 = new Cat();
    cout << endl;

    /* 포인터 변수들은 같은 객체를 가리킬 수 있다.*/
    Cat* cat3 = cat2;
    cout << endl;

    /* cat2객체가 동적으로 생성되었으니 메모리에서 객체 해제 해 준다.*/

    /*
    cat2와 cat3는 동시에 한 객체를 가리키고 있다.
    delete cat2를 통해서 객체를 소멸시켜 주었다.
    그런데 cat3가 이미 소멸된 객체를 다시 소멸시키려 하면 에러가 발생한다.
    이미 소멸된 객체를 다시 소멸시켜서 발생하는 버그를 double free버그라고 한다.
    이와 같은 문제가 발생하는 이유는 생성된 객체의 소유권이 명확하지 않기 때문이다.
    만약 우리가 어떤 포인터에 객체의 유일한 소유권을 부여해서 이 포인터 말고는 객체를 소멸시킬 수
    없다라고 한다면 같은 객체를 두번 소멸시켜 버리는 일은 발생하지 않을 것이다.

    unique_ptr : 객체의 유일한 소유권을 가지는 스마트 포인터
    */
    delete cat2;
    cout << endl;

    /*
    cat3 객체도 동적생성되었으니 메모리에서 객체 해제를 해 준다 

    문법적으로는 문제가 없어보인다, 컴파일 에러도 없다.
    하지만 플레이하면 에러가 발생한다. 런타임 에러는 조심해야 한다.
    */
    //delete cat3;  // x
    cout << endl;

    /* 
    위의 경우를 보면 cat2에 new Cat()으로 생성된 객체의 소유권을 보유한다면
    delete cat2는 가능하고
    delete cat3는 불가능하다.

    이제 unique_ptr로 객체 생성을 해 준다.
    */
    unique_ptr<Cat> uniquePtr2(new Cat());
    cout << endl;

    /*
    unique_ptr들이 같은 객체를 가리키면 어떻게 되나..
    만약에 unique_ptr를 복사하려고 한다면?
    */
    unique_ptr<Cat> uniquePtr3(new Cat());
    cout << endl;
    /* uniquePtr4가 uniquePtr3를 가리킬 수 없다.*/
    //unique_ptr<Cat> uniquePtr4 = uniquePtr3;    // x
    cout << endl;

    /* 
    unique_ptr 소유권 이전하기에 대해 알아보기
    unique_ptr은 복사가 불가능 하지만 소유권을 이전할 수 있다.
    */

    /* get() 함수를 이용하면 실제 객체의 주소값을 반환해 준다.*/
    cout << "uniquePtr3 : " << uniquePtr3.get() << endl;
    cout << endl;

    /*
    uniquePtr3를 uniquePtr4로 강제 이동시켜 버린다. 소유권 이전.
    uniquePtr4는 new Cat()으로 생성된 객체의 소유권을 가지게 되고, uniquePtr3는 아무것도 가리키지 않는다.
    */
    unique_ptr<Cat> uniquePtr4 = move(uniquePtr3);

    cout << "uniquePtr3 : " << uniquePtr3.get() << endl;
    cout << "uniquePtr4 : " << uniquePtr4.get() << endl;
    cout << endl;

    /*
    unique_ptr을 사용하면서 주의 사항

    소유권이 이전된 unique_ptr을 댕글링 포인터(dangling pointer)라고 한다.
    이를 재참조할 시에 런타임 에러가 발생한다.
    따라서 소유권 이전은 댕글링 포인터를 절대 다시 참조하지 않겠다는 확신하에 이동해야 한다.

    소유권을 이동 시킨 후에 원래의 unique_ptr에 접근하지 않도록 조심해야 한다.
    */

    /*
    unique_ptr 안전하게 생성하기
    C++ 14부터 unique_ptr객체를 안전하게 생성할 수 있는 make_unique() 함수를 제공한다.
    */
    unique_ptr<Cat> uniquePtr5 = make_unique<Cat>();
    cout << endl;

    /*
    unique_ptr를 요소로 가지믄 벡터 컨테이너에 대해 알아보기
    */
    /*
    빌드하면 에러가 생긴다. 
    삭제된 unique_ptr의 복사 생성자에 접근하였기 때문이다.
    기본적으로 vector의 push_back 함수는 전달된 매개 변수를 복사해서 전달되기 때문에
    이와 같은 문제가 발생한다.
    unique_ptr은 소유권이 유일해야 한다. 매개변수에 복사해서 전달하면 유일하지 않는다.
    unique_ptr은 move()함수를 통해서 소유권을 이전해서 vector 컨테이너에 전달해 주어야 한다,
    */
    vector<unique_ptr<Cat>> vector5;
    unique_ptr<Cat> uniquePtr6(new Cat());
    cout << endl;

    //vector5.push_back(uniquePtr6);    //x
    cout << endl;
    /* 이를 방지하기 위해서는 명시적으로 uniquePtr6를ㄹ 벡터 컨테이너 안으로 이동시켜주어야한다.*/
    vector5.push_back(move(uniquePtr6));
    cout << endl;

    /* 
    emplace_back() 함수 : emplace_back() 함수를 이용하면 vector 컨테이너에 unique_ptr 객체를
    직접 생성하면서 넣을 수 있다. 불필요한 과정을 생략 가능하다.
    */
    vector<unique_ptr<Cat>> vector6;
    vector6.push_back(unique_ptr<Cat>(new Cat()));
    cout << endl;

    /*
    emplace_back() 함수는 전달된 매개변수를 직접unique_ptr<Cat>의 생성자에 전달해서
    vector 컨테이너의 맨 뒤에 unique_ptr<Cat> 객체를 생성한다.
    따라서 불필요한 이동 연산이 필요없게 된다.
    */
    vector6.emplace_back(new Cat());
    cout << endl;

}

0개의 댓글