[C++기초] 15. 스마트 포인터

INDEX

  1. 기존 포인터의 문제점
  2. unique_ptr
  3. 자동 메모리 관리
  4. 가비지 컬렉션
  5. 참조 카운팅
  6. shared_ptr
  7. weak_ptr
  8. 비교 : 강한참조 약한참조

기존 포인터의 문제점

스마트 포인터들

기존 포인터의 문제점

unique_ptr

유니크 포인터

예시 : 유니크 포인터 만들기(C++)

#include<memory>
#include"Vector.h"
int main()
{
    //포인터 부호(*)가 없음
    std::unique_ptr<vector> myVector(new Vector(10.f, 30.f));

    //포인터처럼 동작
    myVector->Print();

    //delete가 없음
    return 0;
}

유니크 포인터는 다음 세가지 경우에 적합하다

문제 : 원시 포인터를 공유

Vector* vectorPtr = new Vector(10.f, 30.f);
std::unique_ptr<Vector> vector(vectorPtr);
std::unique_ptr<Vector> anotherVector(vectorPtr);
anotherVector = nullptr;
  • 위의 상황에서 포인터가 공유가 됨.
    • 포인터를 단독으로 소유한다는 개념이 깨짐

C++ 14 이후 해결책

#include<memory>
#include"Vector.h"
int main()
{
    // 힙 할당 불필요. 단지 std::make_unique<> 사용법을 보여주기 위함
    std::unique_ptr<Vector> myVector = std::make_unique<Vector>(10.f, 30.f);

    myVector -> Print();

    return 0;
}
  • std::make_unique()를 이용해서 문제 보안

std::unique()가 무슨 일을 할까?

유니크 포인터 만들기

//non-array
template< class T, class... Args >
unique_ptr<T> make_unique(Args&&... args);

//array
template< class T>
unique_ptr<T> make_unique(std::size_t size);
//C++11 (C++14를 쓸 수 없는 경우)
std::unique_ptr<vector> vector(new Vector(10.f, 30.f));
std::unique_ptr<Vector[]> vectors(new Vector[20]);

//C++14 (이걸 쓰자)
std::unique_ptr<Vector> vector = std::make_unique<Vector>(10.f, 30.f);
std::unique_ptr<Vector[]> vectors = std::make_unique<Vector[]>(20);

유니크 포인터 재설정하기

예시 : reset()

int main()
{
    std::unique_ptr<Vector> vector = std::make_unique<Vector>(20.f, 30.f);

    vector.reset(new Vector(20.f, 40.f));

    vector.reset();

    //…
}

reset() vs nullptr

reset()

std::unique_ptr<Vector> vector;
vector.reset()

nullptr

std::unique_ptr<Vector> vector;
vector = nullptr;

포인터 가져오기

예시 : get()

Vector.cpp

void Vector::Add(const Vector* other)
{
	mX += other->mX;
	mY += other->mY;
}

Main.cpp

#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();
	return 0;
}

포인터 소유권 박탁하기

예시 : release()

int main()
{
    std::unique_ptr<Vector> vector = std::make_unique<Vector>(10.f, 30.f);
    Vector* vectorPtr = vector.release();
    //...
}

유니크 포인터 소유권 이전

std::move()

예시 : 유니크 포인터 소유권 이전하기

#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));
    // ...
}

예시 : STL벡터에 요소 추가하기

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));

std::unique_ptr의 비밀 공개

매워 단순화 시킨 예시

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;
}

std::unique_ptr베스트 프랙티스

자동 메모리 관리

자동 메모리 관리

GC나 RefCount를 쓰면 메모리 누수가 없다?

가비지 컬렉션 vs 참조 카운팅

가비지 컬렉션

가비지 컬렉션

가비지 컬렉션의 문제점

참조 카운팅

참조 카운팅

예시 : 수동 참조 카운팅

강한 참조

참조 카운팅의 문제점

shared_ptr

std::shared_ptr

std::shared_ptr만들기

//배열이 아님
template<class T, class... Args>
    shared_ptr<T> make_shared(Args&&... args);

예시 : std::shared_ptr 만들기

#include<memory>
#include"Vectorh.h"
int main()
{
    std::shared_ptr<Vector> vector = std::make_shared<Vecto>(10.f, 30.f);
    // ...
}

포인터 소유권 공유하기

shared_ptr& operator= (const shared_ptr& x) noexcept;

template<class U>
shared_ptr& operator= (const shared_ptr<U>& x) noexcept;

예시 : 포인터 공유하기

int main()
{
    std::shared_ptr<Vector> vector = std::make_shared<Vector>(10.f, 30.f);
    std::shared_ptr<Vector> copiedVector = vector;
    // ...
}

포인터 재설정하기

void reset() noexcept;

예시 : 포인터 재설정하기

int main()
{
    st::shared_ptr<Vector> vector = std::make_shared<Vector>(10.f, 30.f);
    std::shared_ptr<Vector> copiedVector = vector;

    // vector.reset()을 하면 강함 참조 횟수가 2에서 1로 줄어듦
    vector.reset(); // vector = nullptr; 과 같음

    // ...
}

참조 횟수 구하기

` long use_count() const noexcept; `

예시 : 순환참조

//Person.h
#include <iostream>
#include <string>
#include "Pet.h"
class Pet;
class Person
{
public:
	void SetPet(const std::shared_ptr<Pet>& pet);
	Person(const std::string& name);
	Person() = delete;
	~Person();
private:
	std::string mName;
	std::shared_ptr<Pet> mPet;
};
//Pet.h
#include <iostream>
#include <string>
#include "Person.h"
class Person;
class Pet
{
public:
	void SetOwner(const std::shared_ptr<Person>& owner);
	Pet(const std::string& name);
	Pet() = delete;
	~Pet();
private:
	std::string mName;
	std::shared_ptr<Person> mOwner;
};
//Main.cpp
#include<memory>
#include"Person.h"
#include"Pet.h"
int main()
{
	std::shared_ptr<Person> owner = std::make_shared<Person>("Pope");
	std::shared_ptr<Pet> pet = std::make_shared<Pet>("Coco");
	
	std::cout << "Owner : "<< owner.use_count() << "Pet : " << pet.use_count() << std::endl;
	
	owner->SetPerson(pet);
	pet->SetOwner(owner);
	
	std::cout << "Owner : "<< owner.use_count() << "Pet : " << pet.use_count() << std::endl;
	return 0;
}

Result

image

  • 소멸자 호출 안됨!
  • 메모리 누수가 난다

어떻게 고칠까?

weak_ptr

약한 참조

std::weak_ptr만들기

tempalte <class U>
weak_ptr& operator= (const shared_ptr<U>& x) noexcept;

예시 : std::weak_ptr 만들기

#include<memeory>
#include"Person.h"
int main()
{
	std::shared_ptr<Person> owner = std::make_shared<Person>("Pope");
	std::weak_ptr<Person> weakOwner = owner;
	return 0;
}

image

약한 포인터로 공유 포인터 만들기

std::shared_ptr<T> lock() const noexcept;

예시 : 약한 포인터로 공유 포인터 만들기

#include<memory>
#include"Person.h"
int main()
{
    std::shared_ptr<Person> owner = std::make_shared<Person>("Lulu");
    std::waek_ptr<Person> weakOwner = owner;

    std::shared_ptr<Person> lockedOwner = weakOwner.lock()
        return 0;
}

공유 포인터가 존재하는지 확인하기

bool expired() const noexcept;

예시 : 공유 포인터가 존재하는지 확인하기 1

#include<memory>
#include<"Person.h"
int main()
{
    std::shared_ptr<Person> owner = std::make_shared<Person>("Lulu");
    std::weak_ptr<Person> weakOwner = owner;
    
    auto ptr = weakOwner.lock();
    if(ptr == nullptr)
    {
        //이하 코드 생략
    }
    return 0;
}

예시 : 공유 포인터가 존재하는지 확인하기 2

#include<memory>
#include"Person.h"
int main()
{
	std::shared_ptr<Person> owner = std::make_shared<Person>("Pope");
	std::weak_ptr<Person> weakOwner = owner;
	
	if(weakOwner.expired()) // false 반환
	{
		// ...
	}
	return 0;
}
#include<memory>
#include"Person.h"
int main()
{
	std::shared_ptr<Person> owner = std::make_shared<Person>("Pope");
	std::weak_ptr<Person> weakOwner = owner;
	
	owner = nullptr;
	if(weakOwner.expired()) // true 반환
	{
		// ...
	}
	return 0;
}
  • 허나 완전하지 않음
    • 위쪽의 경우 멀티스레드 환경에서는 불안하다….
    • Lock 걸어야 한다

약한 포인터로 순환 참조를 해결해보자

Person.h

#include <iostream>
#include <string>
#include "Pet.h"
class Pet;
class Person
{
public:
    void SetPet(const std::shared_ptr<Pet>& pet);
    Person(const std::string& name);
    Person() = delete;
    ~Person();
private:
    std::string mName;
    std::shared_ptr<Pet> mPet;
};

Pet.h

#include <iostream>
#include <string>
#include "Person.h"
class Person;
class Pet
{
public:
    void SetOwner(const std::shared_ptr<Person>& owner);
    Pet(const std::string& name);
    Pet() = delete;
    ~Pet();
private:
    std::string mName;
    std::weak_ptr<Person> mOwner;
};

image

  • 소멸자 호출!!
  • shared_ptr에서 순환참조시 소멸자 호출 안되는 문제 해결!

비교 : 강한참조 약한참조

std::whared_ptr std::weak_ptr
강한참조 약한참조
강한 참조 카운트를 늘림 약한 참조 카운트를 늘림
직접적으로 사용할 수 있음 직접적으로 사용할 수 없음
원시포인터가 확실히 존재하기 때문 lock을 써서 std::shared_ptr가 여전히 존재하는지 확인해야 함