[C++] 스마트포인터(Smart Pointer)

2015. 7. 14. 14:09프로그래밍/C/C++

728x90
728x90

RAII

  • RAII는 C++에서 자주 쓰이는 idiom으로 자원의 안전한 사용을 위해 객체가 쓰이는 스코프를 벗어나면 자원을 해제해주는 기법이다.
  • C++에서 heap에 할당된 자원은 명시적으로 해제하지 않으면 해제되지 않지만, stack에 할당된 자원은 자신의 scope가 끝나면 메모리가 해제되며 destructor가 불린다는 원리를 이용한 것이다.

스마트 포인터 정의

  • 자원관리 기법에 사용되며 메모리 자원 관리와 경계 검사 등
  • 기존 포인터 변수에서 기능이 추가 된 추상 데이터 타입.

스마트 포인터의 특징

  • 자원을 획득한후, 자원 관리 객체에게 넘긴다
  • 자원관리 객체는 자신의 소멸자를 사용하여 자원이 확실하게 해제 되도록 한다
  • 버그 보완(안전), 자동청소, 자동 초기화를 해준다
  • 스마트 포인터는 C++에서 안전하고 효율적인 코드를 작성하는데 유용하다
  • delete를 이용해 직접 메모리를 해제하지 않고, 스마트 포인터의 소멸자에 존재하는 delete 키워드를 이용해서 메모리를 삭제한다
  • 원본 삭제 및 참조 카운팅 등을 이용해 댕글링 포인터가 되는 것을 막는다

스마트 포인터의 장점

  • Dangling Pointer로 인해 메모리 누수 현상을 방지할 수 있다.

Dangling Pointer란?

  • 첫 번째 - delete로 해제된 메모리를 가리키는 포인터.
#include "stdafx.h"
#include <iostream>

void func(int* ptr);

int _tmain(int argc, _TCHAR* argv[])
{
    int* int_ptr = nullptr;

    func(int_ptr);

    printf("%d", *int_ptr);

    return 0;
}

void func(int* ptr)
{
    int dangling  = 10;
    ptr = &dangling;
}
  • 두 번째 - 스택 지역에서 사라진 메모리를 가리키는 포인터.
#include "stdafx.h"
#include <iostream>

void func(int* ptr);

int _tmain(int argc, _TCHAR* argv[])
{
    int* int_ptr = nullptr;

    int_ptr = new int(10);

    delete int_ptr;

    // dangling
    printf("%d", *int_ptr);

    return 0;
}

사용하는 이유

코드 간략화

자동 해제

자동 초기화

Dangling Pointer 방지

Exception 안전

  • 정상적인 함수 종료에 의한 함수 종료든 Exception이 발생하였든 소멸자는 항상 호출된다.

가비지 컬렉션

  • 일부 언어들은 자동 가비지 컬렉션 기능을 제공하지만 C++은 그렇지 못하다.
  • 스마트 포인터는 가비지 컬렉션 용도로 사용할 수 있다.

효율성

  • 스마트 포인터는 가용한 메모리를 좀 더 효율적으로 사용할 수 있게 하며 할당, 해제 시간을 단축 시킬 수 있다.
  • COW( Copry On Write ) : 1개의 객체가 수정되지 않는 동안 여러 COW 포인터가 해당 객체를 가리킬 수 있도록 하되, 해당 객체가 수정되는 경우 COW 포인터가 객체를 복사 한 후 복사 본을 수정하는 방법
  • 객체가 할당되거나 운용되는 환경에 대해 일부 가정을 세울 수 있는 경우 최적화된 할당 계획이 가능하다.(운영 체제나 응용 프로그램이 변경된다 하더라도 클래스의 코드를 최적화된 할당 계획을 만들 수 있다.)

스마트 포인터의 단점

  • 스마트 포인터가 NULL 인지 체크 불가
  • 상속 기반의 변환 제한
  • 상수 객체에 대한 포인터 지원 제한
  • 구현하기 까다롭다.
  • 이해하기 쉽지 않아 유지보수도 어렵다.
  • 디버깅이 어렵다.

스마트 포인터의 종류

  • STL 라이브러리에 공식직원하는 스마트 포인터는
  • auto_ptr, shared_ptr, unique_ptr, weak_ptr이 있다.
  • 그리고 부스터 라이브러리에서 제공하는 scoped_ptr, shared_array 등이 있다.

auto_ptr

  • 정의: 포인터 변수와 비슷하게 동작하는 객체로써, 가리키고 있는 대상(동적 할당 된 대상)에 대해 auto_ptr 클래스의 소멸자가 자동으로 delete를 호출하는 클래스이다.
  • 특징
    • 복사 시 소멸식 복사를 하기 때문에 소유권을 이전한다. 그러므로 컨테이너에 절대 넣으면 안 된다.
    • 내재된 포인터에 대해 오직 하나만의 소유만을 허용한다.
    • C++11에 이르러 표준에서 제외되었으며 유사한 기능성에, 보다 안정적인 unique_ptr로 대체되었다.
    • 어떠한 자원을 가리키는 auto_ptr의 개수가 둘 이상이면 절대로 안되기 때문에, auto_ptr 객체를 복사하면(복사 생성자 혹은 복사 대입 연산자를 통해) 원본 객체가 가리키는 값을 null로 만든다. 즉, 복사된(copying) 객체만이 그 자원 의 유일한 소유권(ownership)을 갖는다.
    • 소유권이 옮겨지면 일반 포인터와 다르게 null 포인터가 된다.

shared_ptr

  • 정의: 참조 카운팅 방식 스마트 포인터( RCSP : Reference-Counting Smart Pointer )이다.
  • 가리키고 있는 객체에 대해 소유권을 가지지 않고 참조 카운트만 유지하는 포인터이다.
  • 특징
    • 다른 객체를 가리키면서 포인터에 대한 소유권을 공유할 수 있다(aliasing). 해당 포인터에 대한 소유권을 가지지 않고 참조 카운팅만 유지하는 방식이다.
    • 자원을 참조하는 외부 객체의 개수를 관리하고 있다가, 그 개수가 0이 되면 해당 자원을 자동으로 삭제한다.
    • STL 컨테이너 사용이 가능하다.
    • 가비지 컬렉션( Garbage Collection )의 동작과 흡사하지만, 가비지 컬렉션과는 달리 참조 상태가 고리를 이루는 경우( 서로 다른 두 객체가 서로를 참조하는 경우 )는 없앨 수 없다.

unique_ptr

  • 정의: C++11에 이르러 포함되었으며 auto_ptr을 보다 개선한 형태(배열 삭제)를 지원한다.
  • 특징
    • 소유권 독점 방식을 사용하는 스마트 포인터로, auto_ptr과 유사하나 복사 생성자와 대입 연산자가 제공되지 않는다.
    • 복사 생성자와 대입 연산자를 제공하지 않기 때문에 복사를 할 수 없으며, 소유권 이전을 위해서 std::move()를 사용한다.
    • 소유권 해제 시점은 포인터 객체가 가리키는 객체의 소멸, 대입연산, reset 메소드에서 명시적 호출을 통해 값을 변경할 때 이다.
    • 소유권을 독점하는 방식이기 때문에 auto_ptr은 객체에 대한 포인터가 유일하도록 관리해주었어야 하지만, unique_ptr은 스스로 포인터의 유일성을 보장한다.

weak_ptr

  • 정의: shared_ptr의 순환 참조로 발생하는 문제를 해결하기 위해 사용하는 특수한 포인터이다.
  • 특징
    • weak_ptr 은 shared_ptr을 관리하기 위한 reference count에 포함되지 않고 shared_ptr의 객체를 참조만 한다. ( shared_ptr 의 weak reference count로 관리)
    • 약한 참조 성질을 지닌다.( 객체가 살아 있도록 유지 시키지 않고, 단순히 객체가 살아 있는 동안만 참조 가능 )
    • expired 함수를 통해 자신이 참조하고 있는 shared_ptr의 상태를 확인 할 수 있다.
    • weak_ptr 객체는 포인터에 대해 직접 접근이 불가능 하며, 액세스를 원하면 lock 메소드를 통해 shared_ptr로 변환한 뒤, shared_ptr 의 get 메소드를 사용한다
728x90
반응형