{
Vector* myVector = new Vector(10.f, 30.f);
// ==> LOGIC
delete myVector
}
#include <memory>
std::unique_ptr<Vector> myVector(new Vector(10.f, 30.f));// 포인터 부호(*)가 없음
myVector->Print();// 허나 포인터처럼 동작
return 0;// delete가 없음.
// unique_ptr 소멸자에서 new Vector 해제해줌.
std::unique_ptr<Vector> copiedVector1 = myVector;
std::unique_ptr<Vector> copiedVector2(myVector);
Case1 : 클래스에서 생성자/소멸자
class Player
{
private:
Vector* mLocation;
};
Player::Player(std::string name)
: mLocation(new Vector())
{
}
Player::~Player() // 소멸자 필요...
{
delete mLocation;
}
class Player
{
private:
std::unique_ptr<Vector> mLocation;
};
Player::Player(std::string name)
: mLocation(new Vector())
{
}
// 소멸자에 delete 키워드 없음.
Case2 : 지역 변수
#include "Vector.h"
int main()
{
Vector* vector = new Vector(10.f, 30.f);
vector->Print();
delete vector;
}
#include <memory>
##include "Vector.h"
int main()
{
std::unique_ptr<Vector> vector(new Vector(10.f, 30.f));
}
Case3 : STL 벡터에 포인터 저장하기
#include <vector>
#include "Player.h"
int main()
{
std::vector<Player*> players;
players.push_back(new Player("Lulu"));
players.push_back(new Player("Coco"));
for(int i = 0; i < players.size(); ++i) // 모두 지워주는 처리 필요
{
delete players[i];
}
players.clear();
}
#include <vector>
#include "Player.h"
int main()
{
std::vector<std::unique_ptr<Player>> playersList;
players.push_back(std::unique_ptr<Player>(new Player("Lulu")));
players.push_back(std::unique_ptr<Player>(new Player("Coco")));
players.clear();
}
Vector* vectorPtr = new Vector(10.f, 30.f); // 0x40000000
std::unique_ptr<Vector> vector(vecgtorPtr);
// ptr = 0x40000000
std::unique_ptr<Vector> anotherVector(vectorPtr);
// ptr = 0x40000000 문제 발생 더 이상 unique 하게 메모리를 가리키고 있지 않는다.
// 연산자 오버로딩, anotherVector의 ptr을 null로 초기화,
// 그리고 소멸자 호출해서 메모리 해제
anotherVector = nullptr;
// "여기서 스코프 빠져나오면 vector와 anotherVector가 소멸자 호출하면서
// 해제된 메모리를 또 해제하는 참사가 발생..."
#include <memory>
#include "Vector.h"
int main()
{
std::unique_ptr<Vector> myVector = std::make_unique<Vector>(10.f, 30.f);
// make_unique가 알아서 new Vector(10.f, 30.f)
// make_unique를 사용하면 Naked pointer (new Vector(10.f, 30.f))
// 를 참조 할 방법이 없기에 공유 문제 해결 할 수 있음.
}
Vector* vectorPtr = new Vector(10.f, 30.f);
std::unique_ptr<Vector> vector1 =
std::make_unique(vectorPtr);// error
std::unique_ptr<Vector> vector2 =
std::make_unique<Vector>(vectorPtr);// error
std::unique_ptr<Vector> vector3 =
std::make_unique<Vector*>(10.f, 30.f);// error
template<class T, class... Args> // 가변 인자.
unique_ptr<T> make_unique(Args&&... args);// r-value
template<class T>
unique_ptr<T> make_unique<std::size_t size);
std::unique_ptr<Vector> vector(new Vector(10.f, 30.f));
std::unique_ptr<Vector[]> vectors(new Vector[20]);
std::unique_ptr<Vector> vector = std::make_unique<Vector>(10.f, 30.f);
std::unique_Ptr<Vector[]> vectors = std::make_unique<Vector[]>(20);
std::unique_ptr<Vector> vector = std::make_unique<Vector>(10.f, 30.f);
// <Vector>(10.f, 30.f) 해제함
// unique pointer가 new Vector(20.f, 40.f)를 새로 가리키게 함.
vector.reset(new Vector(20.f, 40.f));
// nullptr를 대입함
// 메모리 해제함
vector.reset();
vector.reset();
vs vector = nullptr;
void Vector::Add(const Vector* other)
{
// C++에서 메모리 소멸은 생성한 쪽에서 해제하는 것을 원칙으로 하니
// Naked 포인터를 받아서 소멸 시키지 않을 거라고 기대한다.
// 내가 만들지 않은 라이브러리에서 Naked 포인터를 요청할 때 get()이 필요함.
mX += other->mX;
mY += other->mY;
}
// ...
#include <memory>
#include "Vector.h"
int main()
{
std::unique_ptr<Vector> vector = std::make_unique<Vector>(10.f, 30.f);
std::unique_ptr<Vector> anotherVector = std::make_unique<Vector>(20.f, 40.f);
vector->Add(anotherVector.get());
vector->Print();
}
int main()
{
std::unique_ptr<Vector> vector = std::make_unique<Vector>(10.f, 30.f);
Vector* vectorPtr = vector.release();
// vector에 있던 <Vector>(10.f, 30.f)가 vectorPtr로 이전됨.
// 소멸자 호출 X, vector는 empty가 됨
}
##include <memory>
#include "Vector.h"
int main()
{
std::unique_ptr<Vector> vector = std::make_unique<Vector>(10.f, 30.f);
std::unique_ptr<Vector> anotherVector(std::move(vector));
}
const std::unique_ptr<Vector> vector = std::make_unique<Vector>(10.f, 30.f);
std::unique_ptr<Vector> anotherVector(std::move(vector));
// 컴파일 에러 발생.
// const 니까 unique_ptr 개체 안에 멤버를 수정 할 수 없음.
std::vector<std::unique_Ptr<Player>> players;
std::unique_ptr<Player> coco = std::make_unique<Player>("Coco");
players.push_back(std::move(coco));
std::unique_ptr<Player> lulu = std::make_unique<Player>("Lulu");
players.push_back(std::move(lulu));
// push_back 할 때 move로 옮겨져야 하는 이유는
// 'coco와 players 소멸될 때 소멸한 메모리를 중복해서 소멸하는'
// '사고를 방지하기 위해서'
template<typename T>
class unique_ptr<T> final
{
public:
unique_ptr(T* ptr) : mPtr(ptr) {}
~unique_Ptr(){delete mPtr;}
T* get(){return mPtr;}
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
private:
T* mPTr = nullptr;
};
메모리 소유권을 공유함
참조 카운팅
새로운 개념은 아님, 몇몇 업계에서는 사용하고 있었음...
자동 메모리 관리
주로 쓰는 두 가지 기법이 있음
가비지 컬렉션(Garbage Collection, GC), java와 C#에서 지원
참조 카운팅(Reference Counting, RefCounting), Swift와 애플 Objective-C에서 지원
가비지 컬렉션
보통 트레이싱 가비지 컬렉션(Tracing Garbage Collection)d을 의미
메모리 누수를 막으려는 시도
주기적으로 컬렉션 실행
충분한 여유 메모리가 없을 때 컬렉션이 실행됨
매 주기마다, GC는 루트("root")를 확인함
힙에 있는 개체에 루트를 통해 접근할 수 있는지 판단
접근할 수 없다면, 가비지로 간주해서 해제
Season GC : MS 사가 Garbaget Collection을 최적화 한 역사... 검색
가비지 컬렉션의 문제점
사용되지 않는 메모리를 즉시 정리하지 않음
GC가 메모리를 해제해야 하는지 판단하는 동안 애플리케이션이 멈추거나 버벅일 수 있음.
30fps로 출력되는 게임이 있다고 하면 GC가 일어나면 그 시점에 프레임이 잠시 게임이 멈추는 것 처럼 보임,
결과적으로 게임이 버벅거리는 현상(안드로이드 게임)
아이폰은 GC 기반이 아님
따라서 스무스하게 보여줘야 하는 프로그램에서는 GC가 약점임.
그러나 Office 같은 프로그램에서는 30fps으로 보여줄 필요도 없고 잠깐 멈춘다고 버벅거리지도 않기 때문 상관 없음.
C#에서 Garbage를 안 만들기 위해 어떻게 동작 하였나
참조 카운팅(자동)
int main()
{
Person* person = new Person("Coco");
Person* copiedPerson = person;
copiedPerson->AddRef();
person->Release();
person = nullptr;
copiedPerson->Release();
copiedPerson = nullptr;
}
class Person
{
private:
// 소멸자를 숨겨야 되는 몇 안되는 경우.
// 이렇게 만들면 Person 개체는 스택에 생성 못하고 힙에만 생성할 수 밖에 없다.
// 스택에 생성하면 범위 벗어나는 순간 무조건 소멸자를 호출하니 컴파일 못함.
~Person()
}
unsigned int Person::AddRef()
{
++mRefCount;
return mRefCount;
}
unsigned int Person::Release()
{
--mRefCount;
if(mRefCount == 0)
{
delete this;
}
return mRefCount;
}
Person::Person(const std::string& name)
: mRefCount(1)
, mname(name)
{
}
Person::~Person()
{
}
// COM(DirectX)이 수동 참조 카운팅을 지원
// std::shared_ptr는 이걸 자동으로 해 줌.
강한(Strong) 참조(지금까지 설명한게 강한 참조, 즉 일반적으로 참조하면 강한 참조를 말함.)
A* a = new A();
a->b.Print();
참조 카운팅의 문제점
A* a = new A();
a->b.Print();
a->b.a.Print();
// 개체 A가 개체 B를 참조
// 개체 B가 개체 A를 참조
// 절대 해제되지 않음~
// C++에 해결책이 있음.
GC나 RefCount를 쓰면 메모리 누수가 없다?
가비지 컬렉션 vs 참조 카운팅
#include <memory>
#include "Vector.h"
int main()
{
std::shared_ptr<Vector> vector = std::make_shared<Vector>(10.f, 30.f);
}
[데이터 ptr]----------------> 데이터
[제어 블록 ptr]----------------> 강한 참조 횟수
shared_ptr<T> 약한 참조 횟수
AAllocator, delete, ...etc
int main()
{
std::shared_ptr<Vector> vector = std::make_shared<Vector>(10.f, 30.f);
// ptr : 0x40000000;
// control block : 0x50000000
std::shared_ptr<Vector> copiedVector = vector;
// ptr : 0x40000000;
// control block : 0x50000000
// 데이터 ptr, 제어 블록 ptr 모두 공유 한다.
}
int main()
{
std::shared_ptr<Vector> vector = std::make_shared<Vector>(10.f, 30.f);
std::shared_ptr<Vector> copiedVector = vector;
vector.reset();// vector == nullptr, 참조 하고 있는 것을 비우겠다.
// 참조 카운트는 1이 되고 copiedVector만 가짐
}
int main()
{
std::shared_ptr<Vector> vector = std::make_shared<Vector>(10.f, 30.f);
cout << vector.use_count() << endl;// 1
std::shared_ptr<Vector> copiedVector = vector;
cout << vector.use_count() << endl;// 2
cout << copiedVector.use_count() << endl;// 2
}
// 원시 포인터를 참조하고 있는 shared_ptr의 개수 반환
std::shared_ptr<Person> owner = std::make_shared<Person>("Pope");
std::shared_ptr<Pet> pet = std::make_shared<Pet>("Coco");
Pope is created
Coco is created
Coco is destroyed
Pope is destroyed
// * 스택의 메모리 구조대로 LIFO
class Person
{
public:
void SetPet(const std::shared_ptr<Pet>& pet);
private:
std::shared_ptr<Pet> mPet;
};
class Pet
{
public:
void SetOwner(const std::shared_ptr<person>& owner);
private:
std::shared_ptr<Person> mOwner;
};
std::shared_ptr<Person> owner = std::make_shared<Person>("Pope");
std::shared_ptr<Pet> pet = std::make_shared<Pet>("Coco");
owner->SetPet(pet);
pet->SetOwner(owner);
// Pope is created
// Coco is created
Owner는 Pet을 Pet은 Owner를 참조하기에 스코프를 나가도 소멸자가 호출 되지 않는다.
어떻게 고침??
#include <memory>
#include "Person.h"
int main()
{
std::shared_ptr<Person> owner = std::make_shared<Person>("Pope");
// - 약한 참조를 사용하기 위해서는 강한 참조를 반드시 사용해야 한다.
// - 강한 참조 카운트 : 1
// - 약한 참조 카운트 : 1 <-- 기본이 1이다.
std::weak_ptr<Person> weakOwner = owner;
// - 강한 참조 카운트 : 1
// - 약한 참조 카운트 : 2
}
std::shared_ptr<Person> owner = std::make_shared<person>("Lulu");
std::weak_ptr<Person> weakOwner = owner;
// - 강한 참조 카운트 : 1
// - 약한 참조 카운트 : 1
std::shared_ptr<Person> lockedOwner = weakOwner.lock();
// - 강한 참조 카운트 : 2
// - 약한 참조 카운트 : 1
// lockedOwner = weakOwner 그냥 대입하면 에러
그래서 약한 참조에서 Person을 사용하려면 강한 참조로 바꿔서 사용 해야함.
Person의 다른 참조가 지워지더라도 lockedOwner가 참조하기 때문에 지워지지 않는다.
약한 포인터로 개체 사용 할 수 없음, 사용하고 싶다면 공유 포인터로 만들어야 함.
쓰레드 락 개념이랑 매우 비슷.
공유 포인터가 존재하는지 확인
std::shared_ptr<Person> owner = std::make_shared<person>("Lulu");
std::weak_ptr<Person> weakOwner = owner;
auto ptr = weakOwner.lock();
if(ptr == nullptr)
{
}
// 해제가 되었으면 true 반환.
// 1)
std::shared_ptr<Person> owner = std::make_shared<person>("Lulu");
std::weak_ptr<Person> weakOwner = owner;
if(weakOwner.expired()) // false
{
}
// 2)
std::shared_ptr<Person> owner = std::make_shared<person>("Lulu");
std::weak_ptr<Person> weakOwner = owner;
owner = nullptr
if(weakOwner.expired()) // true
{
}
if(weakOwner.expired()) <-- false
{
}
class Person
{
public:
void SetPet(const std::shared_ptr<Pet>& pet);
private:
std::shared_ptr<Pet> mPet;
};
class Pet
{
public:
void SetOwner(const std::shared_ptr<person>& owner);
private:
std::weak_ptr<Person> mOwner; // 순환 참조로
};
// Pope is created
// Coco is created
// Coco is destroyed
// Pope is destroyed
[강한 참조로만 이루어졌을 때 리스트 소멸]
[S1 ]<->[S2 ]<->[S2 ]<->[S1 ]
[S0 ] [S1 ]<->[S2 ]<->[S1 ]
뒤에 얘들 지워지지 않음.
[prev를 약한 참조로 만들 었을 때 리스트 소멸]
[S1, W0]<->[S1, W1]<->[S1, W1]<->[S1, W1]
[S1, W0] [S0, W1]<->[S1, W1]<->[S1, W1]
[S1, W0] [S0, W1]<->[S1, W1]
[S1, W0] [S0, W1]
[S1, W0]
- 한 방향으로만 연결 되어 있는것과 마찬 가지
- 연결 된 방향으로 연쇄적으로 소멸 됨.
- "강한 참조를 없앤다는 의미는 강한 참조를 하고 있는 개체의 소멸자를 호출하는 것과 같은 의미한다."
shared_ptr
weak_ptr
shared와 weak는 프로그램의 흐름을 이상하게 만든다.