사용이 끝난 자원은 반드시 반환을 해서 다른 작업에서 사용할 수 있도록 해야 함. C++의 경우, 한 번 획득한 자원을 프로그래머가 직접 해제해줘야 함.
제대로 종료하지 않으면, 포인터는 존재하지 않는데
heap에는 해당 객체가 남아있는 메모리 누수 발생
자원의 획득은 초기화다 (RAII)
포인터 객체 == 스마트 포인터 (Smart Pointer)
c++에서 메모리를 잘못된 방식으로 관리했을 때 크게 두 가지 종류의 문제점 발생 가능
메모리 누수
메모리를 사용한 후에 해제하지 않은 경우.
장시간 작동하는 프로그램의 경우 시간이 지남에 따라 점점 사용하는 메모리 양 증가 -> 결과적으로 시스템 메모리 부족해짐
- RAII 패턴 사용해서 해결 가능
double free 버그
이미 해제된 메모리 다시 참조하는 경우.
보통 이런 경우 메모리 error 발생하며 프로그램이 죽는데, 이런 문제가 발생하는 이유는 만들어진 객체의 소유권이 명확하지 않기때문
- unique_ptr 을 통해, 특정 객체에 유일한 소유권을 부여함으로써 해결 가능
- 즉, 유일한 소유권 부여해서 "이 포인터 말고는 객체를 소멸시킬 수 없다!"고 정의해주는 것
#include <iostream>
#include <memory>
class A {
int *data;
public:
A() {
std::cout << "자원을 획득함!" << std::endl;
data = new int[100];
}
void some() { std::cout << "일반 포인터와 동일하게 사용가능!" << std::endl; }
~A() {
std::cout << "자원을 해제함!" << std::endl;
delete[] data;
}
};
void do_something() {
std::unique_ptr<A> pa(new A());
pa->some();
}
int main() { do_something(); }

// pa는 A 클래스의 객체를 가리키는 포인터
std::unique_ptr<A> pa(new A());
// pa가 포인터인 것 처럼 사용하면 됨
pa->some();
unique_ptr를 정의하기 위해서는 템플릿에 인자로 포인터가 가리킬 클래스를 전달하면 됨.
-> 연산자를 오버로드해서 마치 포인터를 다루는 것과 같이 사용할 수 있게 함RAII 패턴 사용 가능 = pa는 스택에 정의된 객체이기 때문에, do_something() 함수가 종료될 때 자동으로 소멸자 호출#include <iostream>
class A {
public:
A(int a){};
/* 복사 생성자 명시적 삭제
-> error 발생*/
A(const A& a) = delete;
};
int main() {
A a(3); // 가능
A b(a); // 불가능 (복사 생성자는 삭제됨)
}
= delete; 를 사용함으로써 복사 생성자를 명시적으로 삭제했기 때문에 컴파일 오류 발생.
unique_ptr는 어떠한 객체를 유일하게 소유해야함. 따라서, 마찬가지로 복사 생성자가 명시적으로 삭제됨.
만약 unique_ptr 를 복사 생성할 수 있게 된다면, 특정 객체를 여러 개의 unique_ptr 들이 소유하게 되는 문제가 발생.따라서, 각각의 unique_ptr 들이 소멸될 때 전부 객체를 delete 하려 해서 앞서 말한 double free 버그가 발생
unique_ptr 는 복사 불가능하지만, 소유권은 이전할 수 있음.
즉, unique_ptr은
#include <iostream>
#include <memory>
class A {
int *data;
public:
A() {
std::cout << "자원을 획득함!" << std::endl;
data = new int[100];
}
void some() { std::cout << "일반 포인터와 동일하게 사용가능!" << std::endl; }
~A() {
std::cout << "자원을 해제함!" << std::endl;
delete[] data;
}
};
void do_something() {
std::unique_ptr<A> pa(new A());
std::cout << "pa : ";
pa->some();
// pb 에 소유권을 이전.
/* pa를 pb에 강제로 이동시켜버림.
따라서 pb가 new A로 생성된 객체의 소유권을 갖게 되고,
pa는 아무것도 가리키고 있지 않음 */
std::unique_ptr<A> pb = std::move(pa);
std::cout << "pb : ";
pb->some();
}
int main() { do_something(); }

pa는 아무것도 가리키고 있지 않음.
pa.get()을 통해 실제 주소값을 확인하면 0 (nullptr) 이 출력됨.
따라서, pa를 이동시킨 후에 pa->some()하면 error 발생.
따라서 소유권을 이동시킨 후, 기존의 unique_ptr을 접근하지 않도록 조심해야 함.
댕글링 포인터 (dangling pointer)
: 소유권이 이전된 unique_ptr를 의미.
: 댕글링 포인터를 재참조할 시 런타임 오류
unique_ptr 의 레퍼런스를 사용하는 것은,
unique_ptr 를 소유권 이라는 중요한 의미를 망각한 채 단순히 포인터의 단순한 Wrapper 로 사용하는 것에 불과.
-> 따라서, 원래의 포인터 주소값을 전달 해 줌으로써 함수에 올바르게 unique_ptr를 전달할 수 있음
#include <iostream>
#include <memory>
class A {
int* data;
public:
A() {
std::cout << "자원을 획득함!" << std::endl;
data = new int[100];
}
void some() { std::cout << "일반 포인터와 동일하게 사용가능!" << std::endl; }
void do_sth(int a) {
std::cout << "무언가를 한다!" << std::endl;
data[0] = a;
}
~A() {
std::cout << "자원을 해제함!" << std::endl;
delete[] data;
}
};
void do_something(A* ptr) { ptr->do_sth(3); }
int main() {
std::unique_ptr<A> pa(new A());
do_something(pa.get());
}

unique_ptr 의 get 함수를 호출하면, 실제 객체의 주소값을 리턴해줌. (위 경우 do_something 함수가 일반적인 포인터를 받고 있음)
이렇게 된다면, 소유권이라는 의미는 버린 채, do_something 함수 내부에서 객체에 접근할 수 있는 권한을 주는 것 !
unique_ptr은 어떤 객체의 유일한 소유권을 나타내는 포인터 이며,unique_ptr가 소멸될 때, 가리키던 객체 역시 소멸됨- 만약에 다른 함수에서
unique_ptr가 소유한 객체에 일시적으로 접근하고 싶다면,get을 통해 해당 객체의 포인터를 전달하면 됨
(즉,pa.get()형태로 넘기며(A* ptr)형태로 인자를 받으면 됨)- 만약에 소유권을 이동하고자 한다면,
unique_ptr를move하면 됨
#include <iostream>
#include <memory>
class Foo {
int a, b;
public:
Foo(int a, int b) : a(a), b(b) { std::cout << "생성자 호출!" << std::endl; }
void print() { std::cout << "a : " << a << ", b : " << b << std::endl; }
~Foo() { std::cout << "소멸자 호출!" << std::endl; }
};
int main() {
// std::make_unique 함수
auto ptr = std::make_unique<Foo>(3, 5);
ptr->print();
}

std::make_unique 함수
std::unique_ptr<Foo> ptr(new Foo(3, 5)); 할 필요 없이 간단하게 만들 수 있음#include <iostream>
#include <memory>
#include <vector>
class A {
int *data;
public:
A(int i) {
std::cout << "자원을 획득함!" << std::endl;
data = new int[100];
data[0] = i;
}
void some() { std::cout << "값 : " << data[0] << std::endl; }
~A() {
std::cout << "자원을 해제함!" << std::endl;
delete[] data;
}
};
int main() {
std::vector<std::unique_ptr<A>> vec;
// vec.push_back(std::unique_ptr<A>(new A(1))); 과 동일
vec.emplace_back(new A(1));
vec.back()->some();
}

emplace_back 함수
unique_ptr<A> 의 생성자에 전달 해서, vector 맨 뒤에 unique_ptr<A> 객체를 생성해버림.여러 개의 스마트 포인터가 하나의 객체를 같이 소유 해야 하는 경우.
특정 자원을 몇 개의 객체에서 가리키는지를 추적한 다음에, 그 수가 0 이 되야만 가리키고 있는 객체를 해제를 시켜주는 방식의 포인터

#include <iostream>
#include <memory>
#include <vector>
class A {
int *data;
public:
A() {
data = new int[100];
std::cout << "자원을 획득함!" << std::endl;
}
~A() {
std::cout << "소멸자 호출!" << std::endl;
delete[] data;
}
};
int main() {
std::vector<std::shared_ptr<A>> vec;
// 모두 같은 객체를 가르키는 shared_ptr
vec.push_back(std::shared_ptr<A>(new A()));
vec.push_back(std::shared_ptr<A>(vec[0]));
vec.push_back(std::shared_ptr<A>(vec[1]));
// vec의 첫번째 원소부터 차례로 지움
// 벡터의 첫번째 원소를 소멸 시킨다.
std::cout << "첫 번째 소멸!" << std::endl;
vec.erase(vec.begin());
// 그 다음 원소를 소멸 시킨다.
std::cout << "다음 원소 소멸!" << std::endl;
vec.erase(vec.begin());
// 마지막 원소 소멸
std::cout << "마지막 원소 소멸!" << std::endl;
// 비로소 A 소멸자 호출
vec.erase(vec.begin());
std::cout << "프로그램 종료!" << std::endl;
}

shared_ptr를 원소로 가지는 벡터 vec 을 정의한 후, vec[0], vec[1], vec[2] 가 모두 같은 A 객체를 가리키는 shared_ptr 를 생성
unique_ptr 와는 다르게 shared_ptr 의 경우 객체를 가리키는 모든 스마트 포인터 들이 소멸되어야만 객체를 파괴하기 때문에, 처음 두 번의 erase 에서는 아무것도 하지 않다가 마지막의 erase 에서 비로소 A 의 소멸자를 호출
즉, 참조 개수가 처음에는 3 이 였다가, 2, 1, 0 순으로 줄어듦
use_count함수를 통해 shared_ptr의 참조 개수 알 수 있음.std::shared_ptr<A> p1(new A());
std::shared_ptr<A> p2(p1); // p2 역시 생성된 객체 A 를 가리킨다.
std::cout << p1.use_count(); // 2
std::cout << p2.use_count(); // 2

앞서 사용했던 std::shared_ptr<A> p1(new A());와 같은 형태로 shared_ptr를 생성하는 것은 바람직한 방법이 아님.
A생성 위한 동적할당, shared_ptr 제어블록 동적할당 -> 총 두번의 동적 할당 발생하기 때문.
std::shared_ptr<A> p1 = std::make_shared<A>();
make_shared함수는 A 의 생성자의 인자들을 받아서, 이를 통해 객체 A 와 shared_ptr 의 제어 블록 까지 한 번에 동적 할당 한 후에 만들어진 shared_ptr 을 리턴
= 아예 두 개 합친 크기로 한 번 할당 하는 것
shared_ptr 은 인자로 주소값이 전달된다면, 마치 자기가 해당 객체를 첫번째로 소유하는 shared_ptr 인 것 마냥 행동함.
A* a = new A();
std::shared_ptr<A> pa1(a);
std::shared_ptr<A> pa2(a);

p1이 ref count 0으로 해당 A 객체를 삭제 하면,
p2는 이미 해제된 객체를 가리키며, 이 객체를 소멸 시키려 하여 오류가 발생.
-> 따라서, shared_ptr 를 주소값을 통해서 생성하는 것을 지양해야함
this 를 사용해서 shared_ptr 을 만들고 싶은 클래스가 있다면,enable_shared_from_this 를 상속 받으면 됨.
#include <iostream>
#include <memory>
class A : public std::enable_shared_from_this<A> {
int *data;
public:
A() {
data = new int[100];
std::cout << "자원을 획득함!" << std::endl;
}
~A() {
std::cout << "소멸자 호출!" << std::endl;
delete[] data;
}
std::shared_ptr<A> get_shared_ptr() { return shared_from_this(); }
};
int main() {
std::shared_ptr<A> pa1 = std::make_shared<A>();
std::shared_ptr<A> pa2 = pa1->get_shared_ptr();
std::cout << pa1.use_count() << std::endl;
std::cout << pa2.use_count() << std::endl;
}

enable_shared_from_this 클래스
shared_from_this 라는 멤버 함수를 정의하고 있음shared_from_this 함수는 이미 정의되어 있는 제어블록을 사용해서 shared_ptr 생성주의
shared_from_this가 잘 작동하기 위해서는 해당 객체의shared_ptr가 반드시 먼저 정의되어 있어야함.
즉,shared_from_this는 있는 제어 블록을 확인만 할 뿐, 없는 제어 블록을 만들지는 않음
순환 참조 문제
weak_ptr 사용
weak_ptr ?
lock 함수 통해 수행lock 함수?
만일 weak_ptr 가 가리키는 객체가 아직 메모리에서 살아 있다면 (즉 참조 개수가 0 이 아니라면) 해당 객체를 가리키는 shared_ptr 을 반환하고, 이미 해제가 되었다면 아무것도 가리키지 않는 shared_ptr 을 반환
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class A {
std::string s;
std::weak_ptr<A> other;
public:
A(const std::string& s) : s(s) { std::cout << "자원을 획득함!" << std::endl; }
~A() { std::cout << "소멸자 호출!" << std::endl; }
void set_other(std::weak_ptr<A> o) { other = o; }
void access_other() {
std::shared_ptr<A> o = other.lock();
if (o) {
std::cout << "접근 : " << o->name() << std::endl;
} else {
std::cout << "이미 소멸됨 ㅠ" << std::endl;
}
}
std::string name() { return s; }
};
int main() {
std::vector<std::shared_ptr<A>> vec;
vec.push_back(std::make_shared<A>("자원 1"));
vec.push_back(std::make_shared<A>("자원 2"));
vec[0]->set_other(vec[1]);
vec[1]->set_other(vec[0]);
// pa 와 pb 의 ref count 는 그대로다.
std::cout << "vec[0] ref count : " << vec[0].use_count() << std::endl;
std::cout << "vec[1] ref count : " << vec[1].use_count() << std::endl;
// weak_ptr 로 해당 객체 접근하기
vec[0]->access_other();
// 벡터 마지막 원소 제거 (vec[1] 소멸)
vec.pop_back();
vec[0]->access_other(); // 접근 실패!
}
