씹어먹는 C++
14장 스마트 포인터 671p-704p
자원 해제 프로그래머가 직접 해제 필요
한번 받은 자원은 직접 해제해주지 않는 이상
프로그램이 종료되기 전 까지 영원히 남아있음
종료시에 운영체제가 알아서 해제
제대로 종료하지 않으면
포인터는 존재하지 않는데
heap에는 해당 객체가 남아있는 메모리 누수 발생
C++ 잘못된 방식 메모리 관리 할시의 문제
1) 메모리를 사용한 후에 해제하지 않은 경우
2) 이미 해제된 메모리를 다시 참조하는 경우
RAII (Resource Acquisition Is Initialization)패턴
자원 (이 경우 메모리) 관리를 스택의 객체 (포인터 객체) 를 통해 수행
C++에서 두 가지 형태의 새로운 스마트 포인터를 제공
특정 객체에 유일한 소유권을 부여하는 포인터 객체
example code
#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;
}
};
//unique_ptr 사용
void do_something() {
//A* pa = new A();와 같다
std::unique_ptr<A> pa(new A());
pa->some();
}
int main() { do_something(); }
자원을 획득함!
일반 포인터와 동일하게 사용가능!
자원을 해제함!
unique_ptr의 복사 생성자는 명시적으로 삭제되어 복사 불가능
어떠한 객체를 유일하게 소유하여야 하기 때문
example code
class A {
public:
A(int a){};
A(const A& a) = delete;
/* 명시적으로 삭제하여
해당 함수를 사용할 수 없도록 함*/
};
int main() {
A a(3); // 가능
A b(a); // 불가능 (복사 생성자는 삭제됨)
}
unique_ptr 복사는 불가능하나 소유권 이전은 가능
즉 이동생성자는 가능함 move로 소유권을 이동시키는 것이므로
std::unique_ptr<A> pb = std::move(pa);
새로 생성된 pb이 소유권을 갖음
pa는 nullptr 즉 아무것도 가르키지 않음
레퍼런스로 넘기면 소유의 권한에 위배
std::unique_ptr<A>& ptr 형태
< 올바르지 않음
원래의 포인터 주소값으로 넘김
pa.get() 형태
로 넘기며(A* ptr)형태
로 인자를 받으면 됨
std::make_unique 함수 사용
템플릿 인자로 전달된 클래스의 생성자에 인자들에 직접 완벽한 전달 수행
example code
#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() {
auto ptr = std::make_unique<Foo>(3, 5);
//std::unique_ptr<Foo> ptr(new Foo(3, 5)); 불필요
ptr->print();
}
생성자 호출!
a : 3, b : 5
소멸자 호출!
복사 생성자가 없는 특성을 지님
vector push_back함수
전달된 인자를 복사하여 집어 넣으므로 사용 불가능
명시적으로 vector안으로 이동 > push_back의 우측값 레퍼런스 오버로딩하여 사용
emplace_back 함수
vector 안에 unique_ptr 을 직접 생성하며 집어넣기 가능
완벽한 전달(perfect forwarding) 을 통해,
직접 unique_ptr<A> 의 생성자에 전달
vector 맨 뒤에 unique_ptr<A> 객체를 생성
즉, 불필요한 이동 과정을 생략 가능
주의
어떤 생성자로 emplace_back하는지 확인 할 것.
요상한게 생성 될 수도 있다.
int 1000인 원소를 원했으나 원소 1000개인 벡터를 추가하게 될 수 도...
example code
int main() {
std::vector<std::unique_ptr<A>> vec;
std::unique_ptr<A> pa(new A(1));
//다음과 동일
//vec.emplace_back(new A(1));
vec.push_back(std::move(pa)); // 잘 실행됨
}
여러 개의 스마트 포인터가 하나의 객체를 같이 소유 해야 하는 경우
특정 자원을 몇 개의 객체에서 가리키는지를 추적한 다음에, 그 수가 0 이 되야만 비로소 해제를 시켜주는 방식의 포인터
example code
#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;
/* 여러개의 ptr로 하나의 객체를 가르키는 것 가능
아래 3개는 모두 같은 객체를 가르키는 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]));
// 벡터의 첫번째 원소를 소멸 시킨다.
std::cout << "첫 번째 소멸!" << std::endl;
vec.erase(vec.begin());
// 그 다음 원소를 소멸 시킨다.
std::cout << "다음 원소 소멸!" << std::endl;
vec.erase(vec.begin());
// 마지막 원소 소멸
std::cout << "마지막 원소 소멸!" << std::endl;
vec.erase(vec.begin());
std::cout << "프로그램 종료!" << std::endl;
}
자원을 획득함!
첫 번째 소멸!
다음 원소 소멸!
마지막 원소 소멸!
소멸자 호출!
프로그램 종료!
A객체의 erase가 ref count가 0이 되어야만 소멸자를 호출
즉 마지막 erase만 소멸자를 호출 가능
여러개의 소유권 가능 > ref count 증가
해당 참조 개수가 0이 되어야 해당 객체 해제 가능
모든 스마트 포인터 들이 소멸되어야만 객체 파괴 가능해짐
p1.use_count();
std::shared_ptr<A> p1(new A());
> 바람직 하지 않음
해당 방법은 동적 할당을 2번 해야함
std::shared_ptr<A> p1 = std::make_shared<A>();
shared_ptr 은 인자로 주소값이 전달이 되면 해당 ptr이 첫번째가 되어버림
즉 주소값을 인자로 넘기면 각 ptr 제어블록 각각 생성
A* a = new A();
std::shared_ptr<A> pa1(a);
std::shared_ptr<A> pa2(a);
p1이 ref count 0으로 해당 A 객체를 삭제 하면
p2는 이미 해제된 객체를 가키며, 소멸 시키려 하여 오류가 발생
this 를 사용해서 shared_ptr 생성을 원하는 클래스가 있다면,
즉 객체 내부에서 자기 자신을 가리키는 shared_ptr 원하면
enable_shared_from_this 를 상속 받아 사용
//불가능
A* a = new A();
std::shared_ptr<A> pa1 = a->get_shared_ptr();
example code
#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;
}
자원을 획득함!
2
2
소멸자 호출!
순환 참조
객체들을 더이상 사용하지 않아도 참조 개수가 절대로 0 이 될 수 없는 상황
shared_ptr 자체에 내재되어 있는 문제 > weak_ptr로 해결
일반 포인터와 shared_ptr 사이에 위치한 스마트 포인터
스마트 포인터 처럼 객체를 안전하게 참조
shared_ptr 와는 다르게 참조 개수 늘리지 않음.
어떤 객체를 weak_ptr가 가리키더라도,
다른 shared_ptr를 사용하지 않으면 이미 메모리에서 소멸
weak_ptr 자체로는 원래 객체를 참조 불가능
반드시 shared_ptr 로 변환해서 사용
참조 하려는 객체가 이미 소멸되었다면 빈 shared_ptr 로 변환
아닐경우 해당 객체를 가리키는 shared_ptr 로 변환
-> lock 함수를 통해 수행
example code
#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; }
//weak_ptr 사용
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(); // 접근 실패!
}
자원을 획득함!
자원을 획득함!
vec[0] ref count : 1
vec[1] ref count : 1
접근 : 자원 2
소멸자 호출!
이미 소멸됨 ㅠ
소멸자 호출!