포인터는 개체의 메모리 주소를 저장하는 변수이다. 

힙에 새 개체를 할당하거나 함수에 다른 함수를 전달하거나 

배열이나 데이터 구조의 요소를 반복할 때 사용한다. 

 

정적 메모리 할당(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++)

learn.microsoft.com

 

'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
Rudolufoo