C++ 스마트 포인터 (Smart Pointer)

2021. 3. 22. 00:15·C++/SmartPointer

스마트 포인터 (Smart Pointer)

RAII (Resource Acquisition Is Initialization)

  • C++의 창시자인 스트롭스트룹이 제안한 디자인 패턴이다.

    • C++ RAII(Resource Acquisition Is Initialization)의 해석을 요약하면 다음과 같다.

      • 객체의 유효성에 대해 다음의 관점이 필요하다.

        • 생성이 완료된 객체는 유효한 객체이어야 한다. 즉, 유효하지 않은 객체가 생성되어서는 안 된다.

        • 필요한 자원들을 획득하지 못한 객체는 유효한 객체가 아니다.

      • 대칭성에 의해, 소멸되어서 더 이상 유효하지 않게 된 객체는 어떤 자원도 가지고 있지 않아야 한다.

  • 메모리 누수를 방지하는 기법이다.

    • 동적으로 할당한 메모리, 혹은 파일을 open한 뒤 early return 혹은 throw 등 예기치 못하게 스택을 되감는 경우에 메모리 누수가 발생할 수 있다.

      main.cpp

      #include <iostream>
      #include "Resource.h"
      
      using namespace std;
      
      void        doSomething()
      {
        Resource* res = new Resource;
      
        if (true)
          return ;
      
        delete res;
      }
      
      int            main()
      {
        doSomething();
      }
      
      /* stdout stderr
      Resource constructed
      */

      Resource.h

      #pragma once
      
      #include <iostream>
      
      class Resource
      {
      public:
        int    data_[100];
      
        Resource()
        {
          std::cout << "Resource constructed\n";
        }
      
        ~Resource()
        {
          std::cout << "Resource destoryed\n";
        }
      };

auto_ptr

  • C++98부터 auto_ptr 클래스가 존재했는데, 앞으로는 사용하지 않아야 되는 클래스이다.

    • 예기치 못한 상황이 발생할 수 있기 때문에, 더 안정적인 unique_ptr 등을 사용하면 된다.
  • 스택에 할당된 인스턴스가 자동으로 소멸되는 것을 이용했다.

  • 아래는 auto_ptr를 비슷하게 구현해보는 예제이다.

    main.cpp

    #include <iostream>
    #include "Resource.h"
    #include "AutoPtr.h"
    
    using namespace std;
    
    void        doSomething()
    {
      AutoPtr<Resource> res(new Resource);
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Resource constructed
    Resource destoryed
    */

    Resource.h

    #pragma once
    
    #include <iostream>
    
    class Resource
    {
    public:
      int    data_[100];
    
      Resource()
      {
        std::cout << "Resource constructed\n";
      }
    
      ~Resource()
      {
        std::cout << "Resource destoryed\n";
      }
    };

    AutoPtr.h

    #pragma once
    
    #include <iostream>
    
    template<class T>
    class AutoPtr
    {
    public:
      T* ptr_ = nullptr;
    
      AutoPtr(T *ptr = nullptr)
        : ptr_(ptr)
      {}
    
      ~AutoPtr()
      {
        if (ptr_ != nullptr) delete ptr_;
      }
    
      T& operator*() const { return *ptr_; }
      T* operator->() const { return ptr_; }
      bool isNull() const { return ptr_ == nullptr; }
    };

Move Semantics

  • 다음과 같은 경우 double free 문제가 발생하여 런타임 에러를 만날 수 있다.

    main.cpp

    #include <iostream>
    #include "Resource.h"
    #include "AutoPtr.h"
    
    using namespace std;
    
    void        doSomething()
    {
      AutoPtr<Resource> res1(new Resource);
      AutoPtr<Resource> res2;
    
      cout << boolalpha;
      cout << res1.ptr_ << endl;
      cout << res2.ptr_ << endl;
    
      res2 = res1;
    
      cout << res1.ptr_ << endl;
      cout << res2.ptr_ << endl;
    }  // 여기서 소멸자가 호출될 때 메모리를 두 번 해제하게 되어 에러 발생
    
    int            main()
    {
      doSomething();
    }

    Resource.h

    #pragma once
    
    #include <iostream>
    
    class Resource
    {
    public:
      int    data_[100];
    
      Resource()
      {
        std::cout << "Resource constructed\n";
      }
    
      ~Resource()
      {
        std::cout << "Resource destoryed\n";
      }
    };

    AutoPtr.h

    #pragma once
    
    #include <iostream>
    
    template<class T>
    class AutoPtr
    {
    public:
      T* ptr_ = nullptr;
    
      AutoPtr(T *ptr = nullptr)
        : ptr_(ptr)
      {}
    
      ~AutoPtr()
      {
        if (ptr_ != nullptr) delete ptr_;
      }
    
      T& operator*() const { return *ptr_; }
      T* operator->() const { return ptr_; }
      bool isNull() const { return ptr_ == nullptr; }
    };

  • 이런 상황을 방지하려면 copy construnctor와 assignment operator를 오버로딩해야 한다.

    • AutoPtr 클래스가 복사 생성자를 호출하거나 대입 연산을 할 때, 내부의 포인터가 옮겨가는 방식으로 구현한다.

    • 이렇게 되면 같은 포인터를 한 인스턴스만 갖도록 하여 메모리 해제의 중복 문제를 피할 수 있다.

    main.cpp

    #include <iostream>
    #include "Resource.h"
    #include "AutoPtr.h"
    
    using namespace std;
    
    void        doSomething()
    {
      AutoPtr<Resource> res1(new Resource);
      AutoPtr<Resource> res2;
    
      cout << boolalpha;
      cout << res1.ptr_ << endl;
      cout << res2.ptr_ << endl;
    
      res2 = res1;  // move semantics
    
      cout << res1.ptr_ << endl;
      cout << res2.ptr_ << endl;
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Resource constructed
    00E8E518
    00000000
    00000000
    00E8E518
    Resource destoryed
    */

    Resource.h

    #pragma once
    
    #include <iostream>
    
    class Resource
    {
    public:
      int    data_[100];
    
      Resource()
      {
        std::cout << "Resource constructed\n";
      }
    
      ~Resource()
      {
        std::cout << "Resource destoryed\n";
      }
    };

    AutoPtr.h

    #pragma once
    
    #include <iostream>
    
    template<class T>
    class AutoPtr
    {
    public:
      T* ptr_ = nullptr;
    
      AutoPtr(T *ptr = nullptr)
        : ptr_(ptr)
      {}
    
      AutoPtr(T& a)
      {
        ptr_ = a.ptr_;
        a.ptr_ = nullptr;
      }
    
      ~AutoPtr()
      {
        if (ptr_ != nullptr) delete ptr_;
      }
    
      AutoPtr& operator = (AutoPtr& a)
      {
        if (&a == this)
          return *this;
    
        delete ptr_;
        ptr_ = a.ptr_;
        a.ptr_ = nullptr;
        return *this;
      }
    
      T& operator*() const { return *ptr_; }
      T* operator->() const { return ptr_; }
      bool isNull() const { return ptr_ == nullptr; }
    };

Syntax vs Semantics


AutoPtr의 한계

  • 다음과 같이 함수 안으로 move semantics가 적용되는 경우, 해당 함수가 끝나면서 메모리가 해제된다.

    • 이후 해당 인스턴스가 또 소멸되는 상황이므로 런타임 에러가 발생한다.
    #include <iostream>
    #include "Resource.h"
    #include "AutoPtr.h"
    
    using namespace std;
    
    void        doSomething2(AutoPtr<Resource> res)
    {
    
    }
    
    void        doSomething()
    {
      AutoPtr<Resource> res1(new Resource);
    
      doSomething2(res1);
    }  // 에러
    
    int            main()
    {
      doSomething();
    }
저작자표시 (새창열림)

'C++ > SmartPointer' 카테고리의 다른 글

C++ 순환 의존성 문제 (Circular Dependency Issues)  (0) 2021.03.24
C++ 이동 생성자와 이동 대입 (Move Constructor and Move Assignment)  (0) 2021.03.24
C++ R-value Reference  (0) 2021.03.24
C++ Syntax vs Semantics  (0) 2021.03.24
'C++/SmartPointer' 카테고리의 다른 글
  • C++ 순환 의존성 문제 (Circular Dependency Issues)
  • C++ 이동 생성자와 이동 대입 (Move Constructor and Move Assignment)
  • C++ R-value Reference
  • C++ Syntax vs Semantics
Caniro
Caniro
  • Caniro
    Minimalism
    Caniro
  • 전체
    오늘
    어제
    • 분류 전체보기 (317)
      • Algorithm (13)
        • 알기 쉬운 알고리즘 (10)
        • Search (1)
        • Sort (2)
      • Arduino (0)
      • C++ (185)
        • Class (46)
        • Exception (6)
        • Library (51)
        • Overloading (10)
        • SmartPointer (5)
        • Syntax (33)
        • TBC++ (23)
        • Templates (9)
        • VisualStudio (2)
      • Embedded (1)
      • Git (4)
      • Java (5)
      • Linux (16)
        • Error (1)
        • Linux Structure (11)
      • MacOS (7)
      • OS (1)
        • Concurrency (1)
      • Python (21)
        • Class (1)
        • Function (2)
        • Syntax (17)
      • Raspberrypi (9)
      • Review (1)
      • Utility (12)
        • VSCode (5)
        • VirtualBox (3)
      • Web (8)
        • Nginx (1)
        • React (3)
        • Django (1)
      • Windows (20)
        • Registry (3)
        • WSL (1)
        • DeviceDriver (6)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    KakaoTalk
    java
    EXCLUDE
    SFC
    시스템 복구
    mspaint
    윈도우
    알림
    그림판
    스프링
    windows
    Solaris 10
    맥북 카카오톡 알림 안뜸
    dism
    vscode
    제외
    logi options
    SunOS 5.1
    스프링 프레임워크 핵심 기술
    unix
    Windows 11
    백기선
    citrix workspace
    spring
    윈도우 명령어
    Workspace
    MacOS
    로지텍 마우스 제스처
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Caniro
C++ 스마트 포인터 (Smart Pointer)
상단으로

티스토리툴바