포인터는 개체의 메모리 주소를 저장하는 변수이다.
힙에 새 개체를 할당하거나 함수에 다른 함수를 전달하거나
배열이나 데이터 구조의 요소를 반복할 때 사용한다.
정적 메모리 할당(Static Memory Allocation)은 프로그램의 생명 주기 동안 자동으로 관리되고
프로그램 종료 시 운영 체제가 자동으로 메모리를 해제하지만
new 연산자로 동적 메모리 할당(Dynamic Memory Allocation)된 메모리는
delete 연산자로 해제하지 않으면 해당 메모리를 계속 사용하고 있다고 인지하여
메모리 누수(Memory Leak)가 발생하게 된다.
메모리 구조 | ||
낮은 주소 (Low memory) 0x00000000 | ||
코드 영역 | 실행할 프로그램의 코드 | |
데이터 영역 | 전역 변수 정적 변수 |
|
낮은 주소에서 높은 주소로 확장 ⬇️ |
힙 영역 | 동적 메모리 할당 런 타임 시 크기가 결정됨 |
높은 주소에서 낮은 주소로 쌓임 ⬆️ |
스택 영역 | 함수 호출 시 지역 변수, 매개 변수, 리턴 주소 등 컴파일 시 크기가 결정됨 |
높은 주소 (High memory) 0xFFFFFFFF |
이처럼 동적 메모리 할당되는 포인터의 경우 명시적으로 메모리에 할당하고 해제해줘야하는데
프로그래머도 사람이다보니 실수를 할 수 있다.
이와 같은 실수를 예방하기 위해 스마트 포인터 (Smart Pointer)를 제공한다.
일반적인 포인터를 Raw Pointer, 원시 포인터라 하고
동적 메모리를 올바르게 관리하기 위한 포인터를 Smart Pointer, 스마트 포인터라 한다.
스마트 포인터는 소멸 시 자동으로 메모리를 해제해주는 기능을 제공하여
메모리 누수를 방지한다.
C++는 다음과 같은 스마트 포인터를 지원한다.
- C++ 표준 라이브러리 스마트 포인터 (POCO* (Plain Old C++ Object) 개체에 대한 포인터)
POCO 개체의 메모리를 안전하게 관리하기 위해 사용됨 - COM** 개체에 대한 스마트 포인터
COM 개체의 참조 카운트를 자동으로 관리 - POCO 개체에 대한 ATL*** 스마트 포인터
POCO 개체와 COM 개체 모두에 사용할 수 있으며 자동 메모리 관리를 제공함
* POCO (Plain Old C++ Object) 란 C++ 클래스와 개체를 뜻함
** COM (Component Object Model)은 서로 다른 프로그램들이 정보를 주고 받을 수 있게 해주는 방법
*** ALT (Active Template Library) 는 COM 프로그래밍을 위해 만들어진 라이브러리
이중에서 C++ 표준 라이브러리 스마트 포인터에 대해 알아보도록 한다.
C++ 표준 라이브러리 스마트 포인터의 종류와 기능은 다음과 같다.
- unique_ptr
기본 포인터로 한 명의 소유자만 허용한다. 새 소유자로 이동할 수 있지만 복사나 공유가 되지 않는다.
자동으로 메모리를 해제한다. - shared_ptr
참조 횟수가 계산 되는 스마트 포인터로 원시 포인터 하나를 여러 소유자에게 할당할 경우 사용된다.
참조 카운트를 통해 마지막 포인터가 파괴될 때 메모리를 해제한다. - weak_ptr
shared_ptr와 함께 사용할 수 있는 특별한 스마트 포인터이다.
weak_ptr는 하나 이상의 shared_ptr 인스턴스가 소유하는 개체에 대한 접근을 제공하지만
참조 수 계산에 포함되지 않는다. 개체를 관찰하는 동시에 해당 개체를 활성 상태로
유지하지 않으려는 경우에 사용된다. (<- 이 뜻은 weak_ptr 자체는 생명 주기에
관여하지 않는다. 다른 shared_ptr에서 소유하고 있을 때에만 접근이 가능하다)
음.. 일단 하나씩 찬찬히 보도록 하자
unique_ptr
스마트 포인터는 자동으로 메모리를 해제한다고 하는데 잘 동작하는지 class를 하나 만들어본다
생성자에 각각 "Raw"와 "Smart"를 넣어주어서 어떤 포인터가 소멸자를 호출하는지 본다.
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Dog
{
string MyName = "";
public:
Dog(string Name){
MyName = Name;
cout << "Hi, I'm " << MyName << '\n';
}
~Dog(){
cout << "Bye, " << MyName << '\n';
}
};
int main()
{
Dog* rawPtr = new Dog("Raw");
unique_ptr<Dog> uniqPtr(new Dog("Smart"));
return 0;
}
메인 함수가 끝나고 난 후에 unique_ptr는 Dog 클래스의 소멸자가 호출된 것을 볼 수 있지만
Raw pointer는 그렇지 않다.
delete 연산자를 사용해서 원시 포인터의 메모리를 해제 하니 다음과 같이 출력된다.
이처럼 unique_ptr는 사용이 끝나면 자동적으로 메모리 해제를 해준다.
그럼 소유권이 유일하다는건 무엇일까..
uniqPtr1은 개채 Dog("Unique1")에 대한 주소를 가지고 있다.
uniqPtr2가 uniqPtr1이 가진 주소를 복사하려 할 때 오류가 발생한다.
unique_ptr는 단독으로 개체 Dog("Unique1")에 대한 소유권을 가지고 있기 때문이다.
소유권을 이전하고 싶으면 move를 사용하면 된다.
shared_ptr
하나의 개체에 대한 주소를 여러 포인터에서 가지고 있는게 shared_ptr 이다.
shared_ptr는 같은 개체를 소유할 수 있고 참조 카운트를 보고 마지막 포인터가 파괴될 때 메모리를 해제한다.
shared_ptr를 초기화하는 방법은 두가지가 있다.
하나는 복사 생성(Copy Assginment)를 하는 방법과
직접 초기화(Direct Initialization)이다.
unique_ptr가 같은 개체를 소유하지 못했지만 shared_ptr는 가능하다.
다음과 같이 shared_ptr를 같이 참조하면 참조 카운트가 올라간다.
weak_ptr
shared_ptr에서 서로를 참조하는 상황이 발생하면 서로 메모리를 사용하고 있다고 인지하여 메모리 해제가 되지않는다
이러한 상황을 예방하기 위한 포인터가 weak_ptr이다.
참조 카운트로 해제 여부를 확인하는 shared_ptr에서 weak_ptr는 소유하는 개체에 대한 접근을 제공하지만
소유자의 수에는 포함되지 않는다.
다음과 같이 클래스를 만들고 서로를 소유하도록 하였다.
class Dog1
{
string MyName = "";
public:
shared_ptr<Dog2> sharedptr;
Dog1(string Name){
MyName = Name;
cout << "Hi, I'm " << MyName << '\n';
}
~Dog1(){
cout << "Bye, " << MyName << '\n';
}
};
class Dog2
{
string MyName = "";
public:
shared_ptr<Dog1> sharedptr;
Dog2(string Name){
MyName = Name;
cout << "Hi, I'm " << MyName << '\n';
}
~Dog2(){
cout << "Bye, " << MyName << '\n';
}
};
main 함수가 종료되어도 메모리에서 해제가 되지 않는다.
이를 방지하기 위한 weak_ptr의 경우 다음과 같다.
class Dog1
{
string MyName = "";
public:
weak_ptr<Dog2> weakptr;
Dog1(string Name){
MyName = Name;
cout << "Hi, I'm " << MyName << '\n';
}
~Dog1(){
cout << "Bye, " << MyName << '\n';
}
};
class Dog2
{
string MyName = "";
public:
weak_ptr<Dog1> weakptr;
Dog2(string Name){
MyName = Name;
cout << "Hi, I'm " << MyName << '\n';
}
~Dog2(){
cout << "Bye, " << MyName << '\n';
}
};
use cout를 확인했을 때 서로를 소유하고 있어도 cout가 올라가지 않는다.
참고 링크 :
https://learn.microsoft.com/ko-kr/cpp/cpp/smart-pointers-modern-cpp?view=msvc-170
'C++' 카테고리의 다른 글
C++ template VS C# generic (2) | 2024.08.08 |
---|---|
Struct vs Class (0) | 2024.06.20 |
SOLID - OOP 설계의 원칙 (0) | 2024.06.08 |
OOP - Object Oriented Programming 객체 지향 프로그래밍 (0) | 2024.06.03 |
std::sort() & Lamda (0) | 2024.05.14 |