본문 바로가기
Programing_Language/C++

스마트 포인터

by neohtux 2020. 12. 21.
728x90

스마트 포인터

  • new와 delete 키워드를 사용 하지 않고

  • 사용이 끝난 포인터를 메모리에서 자동으로 해제 해주는 클래스 템플릿

  • 메모리 누수로부터 안전성을 보장하기 위함

스마트 포인터 종류

  • unique_ptr
  • shared_ptr
  • weak_ptr (TODO) 정리 예정
    • c++11 이후로 auto_ptr은 사라짐.
  • 스마트 포인터는 <memory> 헤더에 정의 되어있음

unique_ptr

  • 특정 객체를 하나의 포인터만 소유 할 수 있도록 소유권 개념의 포인터

  • 다른 포인터와 객체 핸들링을 공유할 수 없기 때문 lvalue 시멘틱이 적용 되지 않는다.

    • ex) ptr1 = ptr2 (ptr1 과 ptr2 는 unique_ptr)
  • 다른 포인터로 객체 참조를 이동시키기 위해 move(이동)시멘틱을 사용한다. ptr1 = std::move(ptr2);
Resource 헤더 접기 / 펼치기
Resource.h

#pragma once

#include<iostream>
using namespace std;

class Resource
{

public:
    int* m_data = nullptr;
    unsigned m_length = 0;

public:

    Resource()
    {
        cout << "Default constructed" << '\n';
    }

    Resource(unsigned length)
    {
        cout << "Length constructed" << '\n';
        this -> m_data = new int[length];
        this -> m_length = length;
    }

    Resource(const Resource& res)
    {
        cout << "copy constructed" << '\n';


        m_data = new int[res.m_length];
        for (int i = 0; i < res.m_length; ++i)
            m_data[i] = res.m_data[i];
    }
    ~Resource()
    {
        cout << "Resource destoryed" << '\n';
        if (m_data != nullptr) delete[] m_data;
    }
    Resource& operator = (Resource& res)
    {
        cout << "copy assingnment" << '\n';

        if (&res == this) return *this;
        if (m_data != nullptr) delete[]m_data;

        m_length = res.m_length;
        m_data = new int[m_length];

        for (unsigned int i = 0; i < m_length; ++i)
        {
            m_data[i] = res.m_data[i];
        }

        return *this;
    }
};
unique_ptr 소스 접기 / 펼치기
int main()
  {

      // unique_ptr을 선언하는 방법 1
      //unique_ptr<Resource> upr;

      // unique_ptr을 선언하는 안전한 방법 2
      {
        auto upr = make_unique<Resource>(10); //크기 10짜리 배열

        //유니크 포인터는 복사가 안됨.
        upr = res1; // error . 컴파일 에러

        upr = move(res1);
      } // upr이 가리키는 객체의 메모리 해제(소멸자 호출)

        return 0;
  }



shared_ptr

  • 특정 객체를 참조하는 스마트 포인터의 갯수가 0이되면 메모리를 해제한다.

  • 하나만 쓰인다면 생성자와 소멸자의 호출시점은 unique_ptr과 같음

  • ex) 2개의 스마트포인터중 1개가 종료되어도 나머지 스마트포인터가 종료되기 전까지 메모리를 해제하지 않는다.

  • 만약 마지막 스마트포인터가 범위를 벗어나면 객체의 메모리 또한 해제한다 (소멸자 호출)

  • 예제 헤더는 위와 같음.
Resource 헤더 접기 / 펼치기
Resource.h
#pragma once

#include<iostream>
using namespace std;

class Resource
{

public:
    int* m_data = nullptr;
    unsigned m_length = 0;

public:

    Resource()
    {
        cout << "Default constructed" << '\n';
    }

    Resource(unsigned length)
    {
        cout << "Length constructed" << '\n';
        this -> m_data = new int[length];
        this -> m_length = length;
    }

    Resource(const Resource& res)
    {
        cout << "copy constructed" << '\n';


        m_data = new int[res.m_length];
        for (int i = 0; i < res.m_length; ++i)
            m_data[i] = res.m_data[i];
    }
    ~Resource()
    {
        cout << "Resource destoryed" << '\n';
        if (m_data != nullptr) delete[] m_data;
    }
    Resource& operator = (Resource& res)
    {
        cout << "copy assingnment" << '\n';

        if (&res == this) return *this;
        if (m_data != nullptr) delete[]m_data;

        m_length = res.m_length;
        m_data = new int[m_length];

        for (unsigned int i = 0; i < m_length; ++i)
        {
            m_data[i] = res.m_data[i];
        }

        return *this;
    }
};
shared_ptr 소스 접기 / 펼치기
int main()
{


    //shared_ptr
    shared_ptr<Resource> ptr1(res2);

    cout << "prev" << '\n';

    {
        auto ptr2 = shared_ptr<Resource>(ptr1);
        cout << " 해제중 ptr2" << '\n';
    }

    // 소멸자가 호출되지 않는다!!  
    // ptr2가 범위를 벗어나도,
    // ptr1이 여전히 가지고있기 떄문


    return 0;
}


weak_ptr

  • weak_ptr은 shared_ptr의 소유권의 순환 문제(circular ref problem)를 해결하기 위해 사용

  • shared_ptr은 참조횟수(ref count)를 기반으로 동작하는 포인터이다.

  • 만약, shared_ptr이 서로가 상대방을 가리키고 있다면, 참조 횟수는 절대 0이 되지 않으므로 메모리 릭이 발생한다.


  1. 의존성 문제로 메모리 릭이 발생하는 예
Person 헤더 접기 / 펼치기
//Person.h

#include <iostream>
#include <memory>
#include <string>
class Person
{
    std::string m_name;
    std::shared_ptr<Person> m_partner;


public:
    Person(const std::string& name) : m_name(name)
    {
        std::cout << m_name << " created\n";
    }
    ~Person()
    {
        std::cout << m_name << " destroyed\n";
    }
    friend bool partnerUp(std::shared_ptr<Person>& p1, std::shared_ptr<Person>& p2)
    {
        if (!p1 || !p2)
            return false;

        // p2는 p1을 참조,
        // p1은 p2를 참조 하는 순환성 관계 성립
        p1->m_partner = p2;
        p2->m_partner = p1;

        std::cout << p1->m_name << " is partnered with " << p2->m_name << "\n";
        return true;
    }
};
Person shared_ptr 소스 접기 / 펼치기
#include <iostream>
#include<string>
#include"Person.h"
using namespace std;


int main()
{
    auto lucy = std::make_shared<Person>("Lucy");
    auto ricky = std::make_shared<Person>("Ricky");

    partnerUp(lucy, ricky);
    //위 헤더의 예제에서는 소멸자 호출이 안됨.
    //shared_ptr 의 참조횟수가 0이 되지 않기 때문
    return 0;
}

  1. weak_ptr을 사용하여 해결
Person 수정된 헤더 접기/펼치기
#pragma once
#include <iostream>
#include <memory>
#include <string>
class Person
{
    std::string m_name;
    //std::shared_ptr<Person> m_partner;

    //weak_ptr로 바꿔준다.
    std::weak_ptr <Person> m_partner;


public:
    Person(const std::string& name) : m_name(name)
    {
        std::cout << m_name << " created\n";
    }
    ~Person()
    {
        std::cout << m_name << " destroyed\n";
    }
    friend bool partnerUp(std::shared_ptr<Person>& p1, std::shared_ptr<Person>& p2)
    {
        if (!p1 || !p2)
            return false;

        // p2는 p1을 참조,
        // p1은 p2를 참조 하는 순환성 관계 성립
        p1->m_partner = p2;
        p2->m_partner = p1;

        std::cout << p1->m_name << " is partnered with " << p2->m_name << "\n";
        return true;
    }

    // weak_ptr를 직접 사용할 수 없고
    // lock을 해서 shared_ptr로 사용해야 한다.
    const std::shared_ptr<Person> getPartner()
    {
        return m_partner.lock();
    }

    const std::string getName()
    {
        return m_name;
    }
};
Person weak_ptr 수정된 소스 접기 / 펼치기
//

#include <iostream>
#include<string>
#include"Person.h"
using namespace std;


int main()
{
    auto lucy = std::make_shared<Person>("Lucy");
    auto ricky = std::make_shared<Person>("Ricky");

    partnerUp(lucy, ricky);
    std::cout << lucy->getPartner()->getName() << '\n';
    return 0;
}

weak_ptr의 한계

  • weak_ptr의 내용물을 사용하려고 할 때
    순환 의존성을 깨기 위해 lock을 사용해야함.
  • lock을 사용하여 반환된 shared_ptr 객체로 사용한다.
300x250

댓글