ProgrammingLang/c++

[C++ 개발자되기] 16. smart pointer

jinkwon.kim 2020. 2. 24. 23:28
728x90
반응형

개요

c++에서의 smart-pointer가 무엇인지 살펴본다. 

 

Smart pointer란?

    - 포인터처럼 동작하는 클래스 템플릿으로, 사용이 끝난 메모리를 자동으로 해제해 줍니다.

 

사용 방식

생성

    - smart_pointer에 자료 형을 동적 할당

소멸

    - 클래스 이기때문에 사용이 끝나면 소멸자가 알아서 메모리 해제하고 사라짐

Ex)

    - smart_pointer<int> sp = new int(5);

 

Smart pointer 좋은점

개발하다 보면 메모리 할당 후 까먹는 경우가 매우 많다 그러나 smart pointer를 사용하면 이런 걱정을 안해도된다.

, 다만 해제된 smart pointer 사용하는 헤프닝이 발생할 수는 있다.

 

Smart pointer 종류 및 핵심 개념

1. unique_ptr

    - unique_ptr은 하나의 스마트 포인터만이 특정 객체를 소유할 수 있도록, 객체에 소유권 개념을 도입한 스마트 포인터입니다.

2. shared_ptr

    - shared_ptr은 하나의 특정 객체를 참조하는 스마트 포인터가 총 몇 개인지를 참조하는 스마트 포인터입니다.

3. weak_ptr

    - weak_ptr은 하나 이상의 shared_ptr 인스턴스가 소유하는 객체에 대한 접근을 제공하지만, 소유자의 수에는 포함되지 않는 스마트 포인터입니다.

 

unique_ptr

    아래 코드의 예시는 unique_ptr의 생성과 다른 unique_ptr 변수에 생성한 unique_ptr을 할당하는 코드 입니다.

unique_ptr를 다른 unique_ptr에 할당 하기 위해서는 std::move로 해주어야 합니다. 왜냐하면  대입 연산이 안되기 때문입니다. 대입 연산이 안된다는 뜻은 객체에 대한 소유권을 다른 변수와 공유하지 않게 다는 뜻 입니다. 그래서 std::move를 통해서 객체에 대한 소유 권을 이전해 주어야 합니다.

 

* std::move *

std::move 는 이름 때문에 사용하는 동시에 어떤 이동 작업이 이뤄질 것 같지만, 실제로 Lvalue 를 Rvalue 로 casting 해줄 뿐입니다.

참조 연산자(&)와 기능을 동일 한데 원본의 값을 다음과 같이 변경 합니다. 

    - pointer 일경우 : nullptr

    - string 일 경우 : ""

    - int 일 경우 : 값만 복사

 

* 유의 사항*

    - unique_ptr을 쉽게만드는 make_unique() 은 c++14부터 지원 합니다. 

    - 대체로 std::shared_ptr 객체는 그 크기가 std::unique_ptr 객체의 두 배이며, 제어 블록에 관련된 추가 부담을
      유발하며, 원자적 참조 횟수 조작을 요구한다.

class Smartptr
{
public:
    Smartptr() {
        std::cout << "생성된다" << std::endl;
    }
    ~Smartptr(){
        std::cout << "소멸된다" << std::endl;
    }
    std::string name = "smartptr name";
};

int main(int argc, char** argv)
{
    std::unique_ptr<Smartptr> up(new Smartptr());

    std::cout << "UP name : " <<  up->name<< std::endl;
    std::unique_ptr<Smartptr> up1 = std::move(up);
    std::cout << "UP1 name : " <<  up1->name<< std::endl;
    std::cout << "move1" <<std::endl;
    if (up == nullptr) {
        std::cout << "up1은 nullptr 이다" << std::endl;
    } else {
        std::cout << "up1은 nullptr 이 아니다." << std::endl;
    }

    return 0;
}

 

shared_ptr

    shared_ptr은 하나의 특정 객체를 참조하는 스마트 포인터가 총 몇 개인지를 참조하는 스마트 포인터입니다.

이렇게 참조하고 있는 스마트 포인터의 개수를 참조 횟수(reference count)라고 합니다.

    1. 참조 회수 증가 상황

        - 생성한 shared_ptr을 다른 shared_ptr에 복사 생성자로 넘겨줄때 

        - 생성한 shared_ptr을 다른 shared_ptr에 대입 할때

    2. 참조 횟수 감소 상황

        - 최초 생성한 shared_ptr를 참조 하는 변수들이 하나씩 수명이 다할 때마다 1씩 감소합니다.

    3. 메모리 해제 상황 

        - 참조 횟수가 0이 되면 delete 키워드를 사용하여 메모리를 자동으로 해제합니다.

 

중요한 점은 shared_ptr도 결국 참조 연산자와 비슷합니다. 같은 메모리 번지를 모두 바라보고 있기 때문입니다. 다만 참조 count를 시용해서 최초 객체의 메모리르 해제 한다는게 추가 되었다고 보면 됩니다.

아래 Code는 class의 name에 대한 메모리 주소를 확인 한 것입니다. 

#include <iostream>
#include <memory>


class Smartptr
{
public:
    Smartptr() {
        std::cout << "생성된다" << std::endl;
    }
    ~Smartptr(){
        std::cout << "소멸된다" << std::endl;
    }
    std::string name = "smartptr name";
};

int main(int argc, char** argv)
{
    std::shared_ptr<Smartptr> sp(new Smartptr);
    std::cout << "sp use count : " << sp.use_count() << std::endl;
    std::cout << "sp name addres  : " << &(sp->name) << std::endl;
    std::shared_ptr<Smartptr> sp1(sp);
    std::cout << "==============================" << std::endl;
    std::cout << "sp use count : " << sp.use_count() << std::endl;
    std::cout << "sp1 use count : " << sp1.use_count() << std::endl;
    std::cout << "sp name addres  : " << &(sp->name) << std::endl;
    std::cout << "sp1 name addres : " << &(sp1->name) << std::endl;
    std::shared_ptr<Smartptr> sp2 = sp;
    std::cout << "==============================" << std::endl;
    std::cout << "sp use count : " << sp.use_count() << std::endl;
    std::cout << "sp1 use count : " << sp1.use_count() << std::endl;
    std::cout << "sp2 use count : " << sp2.use_count() << std::endl;
    std::cout << "sp name addres  : " << &(sp->name) << std::endl;
    std::cout << "sp1 name addres : " << &(sp1->name) << std::endl;
    std::cout << "sp2 name addres : " << &(sp2->name) << std::endl;
    std::shared_ptr<Smartptr> sp3 = sp1;
    std::cout << "==============================" << std::endl;
    std::cout << "sp use count : " << sp.use_count() << std::endl;
    std::cout << "sp1 use count : " << sp1.use_count() << std::endl;
    std::cout << "sp2 use count : " << sp2.use_count() << std::endl;
    std::cout << "sp3 use count : " << sp3.use_count() << std::endl;
    std::cout << "sp name addres  : " << &(sp->name) << std::endl;
    std::cout << "sp1 name addres : " << &(sp1->name) << std::endl;
    std::cout << "sp2 name addres : " << &(sp2->name) << std::endl;
    std::cout << "sp3 name addres : " << &(sp3->name) << std::endl;

    return 0;
}

읽어 보면 좋음 : http://www.secmem.org/blog/2019/12/15/effective-modern-cpp-2/

weak_ptr

weak_ptr은 하나 이상의 shared_ptr 인스턴스가 소유하는 객체에 대한 접근을 제공하지만, 소유자의 수에는 포함되지 않는 스마트 포인터입니다.

 

shared_ptr은 참조 횟수(reference count)를 기반으로 동작하는 스마트 포인터입니다.

만약 서로가 상대방을 가리키는 shared_ptr를 가지고 있다면, 참조 횟수는 절대 0이 되지 않으므로 메모리는 영원히 해제되지 않습니다.

 

이렇게 서로가 상대방을 참조하고 있는 상황을 순환 참조(circular reference)라고 합니다.

weak_ptr은 바로 이러한 shared_ptr 인스턴스 사이의 순환 참조를 제거하기 위해서 사용됩니다.

 

 

https://jungwoong.tistory.com/50

 

 

https://docs.microsoft.com/ko-kr/cpp/cpp/smart-pointers-modern-cpp?view=vs-2019

 

스마트 포인터(최신 C++)

자세히 알아보기: 스마트 포인터 (최신 c + +)

docs.microsoft.com

 

728x90
반응형