heap, stack
메모리 공간에는 힙 영역과 스택 영역이 있다.
스택 영역은 함수가 호출될때 생성되는 지역 변수와 매개 변수가 저장되는 공간이다. 정적 데이터가 할당되고 해제된다. L나중에 할당된 데이터가 먼저 해제되는 LIFO(Last in First Out) 후입선출 방식으로 동작한다.
힙 영역은 프로그램이 동작하면서 동적으로 할당되고 해제되는 공간이다. new, delete등의 방법으로 동적으로 메모리를 할당, 해제할 수 있다. 먼저 할당된 메모리가 먼저 해제되는 FIFO(First in First Out)선입선출로 동작한다.
스마트 포인터가 생기게된 이유
RALL(Resource acquisition is initialization): 자원의 획득은 초기화다.
=> 자원을 할당한 객체를 통해 지움도 동시에 수행해라
C++이나 C에서는 C#처럼 가비지 컬렉터가 없기 때문에 사용자가 할당한 메모리를 모두 해제해줘야 된다.
하지만 코드가 방대할수록 이를 지키기는 매우 어렵고, 메모리 릭이나 더블프리등과 같은 문제가 발생한다.
메모리 릭은 할당한 메모리를 해제해주지 않아 메모리 누수가 발생하는 문제이고,
더블 프리는 한번 해제한 메모리를 한번더 해제하게 되면 생기는 문제이다.
이처럼 휴먼에러를 방지하기 위해 스마트 포인터가 생겼다.
모든 스마트 포인터의 특징으로는 사용자가 delete를 하지 않아도 알아서 해준다(동적 할당 메모리를 자동으로 해제).
스마트 포인터 종류
1.unique_ptr
=> 특정 객체에 대한 유일한 소유권을 부여하는 포인터다.
특징으로는 한값만 참조가 가능하다.(이미 참조된 값을 절대 할당하지 못하게 한다.)
장점으로는 한놈만 바라보기 때문에 관리하기 편하다.
단점으로는 소유권을 넘길 때 일반적인 방법이 아닌 std::move() 를 통해 해줘야 된다.
생성은 new int 또는 make_unique<int>로 가능하다. new는 힙에 메모리를 할당해주기 때문에 힙 오버플로우가 발생할 수도 있다. 후자가 예외처리 및 최적화도 해주기 때문에 더 안전하다.
함수를 인자로 넘길때 일반적으로 복사로 넘기는 것이 불가능하다.(함수 인자로 사용시 복사되면 목적에 위배되기 때문)
때문에 함수인자를 참조로 넘기거나 std::move()를 사용하여 넘길 수 있다.
2. shared_ptr
=>말 그래도 공유가 목적인 포인터다. unique보다 느슨하게 관리할때 사용한다.
특징으로는 참조 수가 0일때 제거된다.
첫번째 단점으로는 일반 주소가 전달되면 자신이 소유권을 가지는 줄 안다.(각자 컨트롤 블록을 가지고, 참조 수를 각자 셈) 이 단점은 직접 전달을 해주면 해결 가능하다.
두번째는 치명적인 단점으로 순환 참조가 불가능하다. 이는 weak_ptr로 해결이 가능하긴 하다.
생성은 new int또는 make_shared로 가능하고, 무조건 후자가 좋다.
new로 생성시 만들고 컨트롤 블록에 넣지만, make_shared는 생성시 만들면서 컨트롤 블록에 넣기 때문이다.
enable_shared_from_this<data>를 상속 받고 shared_from_this()를 쓰면 getsharedPtr이런식으로 넘기면서 더블프리가 발생하는 문제점(첫번째 단점)을 해결할 수 있다.
3. weak_ptr
=>거의 사용하지 않는다.(구조가 잘못됬을 때 소방관처럼 매꿀때 주로 사용한다.)
특징으로는 소유권을 가지지 않는다.