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이 되지 않으므로 메모리 릭이 발생한다.
- 의존성 문제로 메모리 릭이 발생하는 예
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;
}
- 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
'Programing_Language > C++' 카테고리의 다른 글
c++ 함수 포인터 (0) | 2020.12.16 |
---|---|
assert (단언하기) in C++ (0) | 2020.12.15 |
템플릿(Template) (2/2) (템플릿 특수화) (0) | 2020.12.14 |
템플릿(Template) (1/2) (0) | 2020.12.13 |
2차원 동적 배열 할당과 해제 (배열 vs 벡터) (0) | 2020.12.07 |
댓글