반응형

Intro


안녕하세요. 이번 포스팅에서는 C++ STL에서 메모리 관리를 위해 만들어진 shared_ptr과 unique_ptr에 대한 정리를 하도록 하겠습니다. C/C++는 동적으로 할당한 메모리를 사용한 후 해제하지 않으면 메모리 누수가 발생합니다. 보통 개발하다 보면 malloc나 new로 메모리를 할당하고 제대로 해제하지 않아 메모리 누수가 자주 자오죠 ㅎㅎ. 저도 이런 실수를 자주 합니다. 실수 없이 할당된 메모리를 완벽하게 해제해주면 상관없지만 대형 프로젝트에서는 필연적으로 발생합니다.

그래서 Java나 C#을 C++과 비교할 때 메모리 관리에 대한 차이점이 나옵니다. Java나 C#은 메모리를 자동으로 관리합니다. 프로그래머가 메모리를 할당하면 자동으로 해제를 해주기 때문에 프로그래머 입장에서는 편리합니다. 하지만 보통 메모리를 자동으로 관리하는게 어떻게 보면 편하고 좋을 수도 있지만, 꼭 좋은 것만은 아닙니다. Java나 C#은 성능에 문제가 된다면 가차 없이 메모리를 해제하여 가비지 컬렉션이 발생합니다. 가비지 컬렉션이 자주 발생하는 건 리소스를 많이 소비하기 때문에 너무 자주 작동하면 성능상 좋지 않습니다. 하지만 메모리를 자동으로 관리해주는 것은 분명 편리한 기능입니다. 그래서 C++11에서 STL 라이브러리로 메모리를 자동으로 해제시켜 주는 shred_ptr와 unique_ptr이라는 라이브러리가 생겼습니다. 그리고 동적으로 할당한 메모리 해제를 자동으로 관리해주는 것을 스마트 포인터라고 합니다.

shared_ptr


많은 객체나 데이터를 다루다 보면 배열식으로 동적 할당을 해야 할 때가 자주 있습니다. shared_ptr도 배열로 할당이 가능합니다. 간단하게 기존 C++의 new와 shared_ptr을 사용하여 int 배열을 만들어 비교해 보도록 하겠습니다.

#include <iostream>
#include <memory>
using namespace std;
int main(void)
{
    shared_ptr<int>shared_ptr_array_test(new int(10));
    int* array_test = new int[10];

    for (auto i = 0;i < 10;i++)
    {
        shared_ptr_array_test.get()[i] = i;
        array_test[i] = i;
    }
    cout << "shared_ptr출력" << endl;
    for (auto i = 0;i < 10;i++)
        cout <<shared_ptr_array_test.get()[i] << endl;

    cout << "일반 동적할당 출력" << endl;
    for (auto i = 0;i < 10;i++)
        cout << array_test[i] << endl;

    delete[] array_test;
}

결과

결과를 보면 new와 shared_ptr의 결과 값이 같습니다. 하지만 가장 큰 차이는 new는 필연적으로 delete를 해줘야 하지만 shared_ptr은 자동으로 메모리 해제가 됩니다.

unique_ptr


unique_ptr도 앞에서 설명한 shared_ptr처럼 동적으로 할당된 객체를 관리하는 스마트 포인터 입니다. shared_ptr과 다른 점은 이름에 나타나듯이 객체를 독점적으로 관리한다는 것입니다. 사실 이 말이 처음에는 이해가 가지 않았습니다. 구글에 검색해 봤을 때 unique_ptr은 "unique_ptr를 사용하면 동적으로 힙 영역에 할당된 인스턴스에 대한 소유권을 얻고 unique_ptr 객체가 사라지면 해당 인스턴스를 해제하게 됩니다"라고 설명되어 있습니다. 이 의미를 곰곰이 생각해보니 unique_ptr로 선언된 객체는 누구에게도 공유되지 않고, 스코프에서 벗어날 때 메모리에서 자동으로 삭제가 됩니다. 또한 복사 생성자와 할당 연산자가 구현되어있지 않기 때문에 복사를 할 수 없고, 이동만 가능합니다. 사용하는 방법은 shared_ptr과 비슷하기 때문에 코드는 넘어가도록 하겠습니다.

그렇다면 unique_ptr은 어디에 사용하는게 좋을까요..? 그건 바로 클래스의 멤버 변수로 활용할 때 유용합니다. 소멸자가 호출될 때 알아서 해제되기 때문에 편리합니다.

마무리


이번 포스팅에서는 C++ STL의 스마트 포인터에 대해 다뤄봤습니다. 하지만 포스팅 내용은 많이 허접합니다. 그냥 기본적인 스마트포인터에 대한 개념을 정리한 거라고 보시면 될 것 같습니다. 배열 객체를 다루는 것만 봤는데 실제로 다른 객체를 다루면.. 기본적으로 코드가 길어지고 귀찮아서.. 이 정도만 했습니다. 스마트 포인터에서 객체의 포인터를 얻을 때는 get()을 사용하고, 참조를 얻을 때는 opereator(->)을 사용합니다. 그리고 reset() 함수를 사용하면 기존에 관리하던 객체를 다른 객체로 교체가 가능합니다. 이런 것들을 다뤘어야 했는데... 죄송합니다.

스마트 포인터를 자주 사용하면 분명이 이전보다 메모리 관리가 쉬워집니다. 하지만 생각 없이 사용하다 보면 큰 오류를 범할 수 있습니다. 큰 오류의 원인 중 하나가 순환 참조입니다. 순환 참조는 java나 C#에서도 자주 발생하기 때문에 주의해야 합니다. 순환 참조는 단순하기 때문에 이런 실수는 좀처럼 하지 않겠지만, 대형 프로젝트에서는 주의하지 않으면 충분히 발생할 수 있는 문제이기 때문에 주의가 필요합니다.

그럼 이상으로 포스팅을 마치겠습니다. 감사합니다.

반응형

'[C++ STL]' 카테고리의 다른 글

[C++ STL - chrono(시간 측정)]  (0) 2021.06.30
[C++ STL - forward_list]  (0) 2021.06.24
[C++ STL - tuple]  (0) 2021.05.21
[C++ STL - range based for]  (0) 2021.05.13
[C++ STL - 람다(lambda)]  (0) 2021.05.04

+ Recent posts