반응형
반응형

0. 요약

  1. new로 생성한 객체를 스마트포인터에 저장하는 코드를 별도의 한 문장으로 만들자
    • shared_ptr pw(new xxx);
  2. 이유
    • new 실행과 해당 자원을 스마트포인터 생성자로 전달하는 사이에 예외 발생시 자원 누수 발생!

1. 문제 사항

  1. 가정
    • 처리 우선 순위를 알려주는 함수 존재
    • 동적 할당한 Widget 객체에 대해 어떤 우선순위에 따라 처리를 적요하는 함수 존재
int priority();
void processWidget(std::shared_ptr<Widget> pw, int priority);

// processWidget을 사용
processWidget(new Widget, proiority());
  1. 위의 processWidget() 함수가 동작할까?
    • shared_ptr의 생성자는 explicit로 선언되어 있음
    • new Widget으로 만들어진 포인터가 shared_ptr 타입으로 암시적 변환이 되지 않음!
    • 아래와 같이 명시적으로 생성 후 호출하도록 변경
processWidget(std::shared_ptr<Widget>(new Widget), priority());
  1. 하지만 위의 문장은 자원이 누수될 가능성이 존재!
    • 위의 한 문장을 수행전 매개 변수 인자를 평가하는 순서 가짐
    • 본 예시에서는 3가지 단계를 거침
    • "new Widget" 을 실행 => shared_ptr 생성자를 호출 => priority() 함수 호출
    • 문제는 3개의 단계의 순서가 컴파일러 마다 다름!
  2. 만약 호출 순서가 아래와 같고, priority 에서 예외가 발생했다면?
    • "new Widget" 실행
    • priority() 실행
    • shared_ptr 생성자 호출
  3. new Widget으로 할당된 메모리가 해제되지 않아 누수 발생!

2. 해결 방법

  1. Widget를 생성해서 RAII에 저장하는 코드를 별도 한 문장으로 만들자!
  2. 그리고, 나머지 문장을 실행하자!
std::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());

참고

  1. Effective C++
반응형
반응형

0. 요약

  1. 자원 관리 객체에서 자원을 관리하자.(RAII 사용)
  2. 자원 관리 객체는 소멸자에서 해당 자원을 해제 하자!

1. 메모리가 누수될 수 있는 상황 가정

  1. 구현한 내용
    • 투자를 모델링한 기본 클래스 Investment
    • Investment 계열의 클래스를 반환하는 팩토리 함수 createInvestment
  2. 사용부
    • createInvestment를 이용해 Investment 계열 클래스 생성
    • 사용 후 객체 반환(delete)
  3. 문제 사항
    • 삭제 전에 return 문이 있을 경우
    • 삭제 전에 goto문에 의해 루프를 빠져 나갈 경우
    • 삭제 전에 예외가 던져질 경우
// 투자를 모델링한 기본 클래스
class Investment{...};

// Invesment를 상속한 클래스를 return하는 팩토리 함수
Invesment* createInvestment();

void f(){
   Invesment *pInv = createInvestment();
   ...
   delete pInv;
}

2. 해결 방안

  1. 자원을 획득한 후에 자원 관리 객체에게 넘기자!
    • createInvestment를 통해 만든 자원을 auto_ptr에게 넘겨 초기화하는데 사용
    • RAII라고 불림
    • 자원 획득과 자원 관리객체의 초기화가 한문장에서 이루어지는 것이 일상적임
  2. 자원 관리 객체는 자신의 소멸자를 사용해 자원이 확실히 해제되도록 하자!
    • 소멸자는 객체가 소멸될 때 자동으로 호출됨
    • 소멸자가 호출될 때 자원을 해제하면 자동으로 확실히 해제됨
    • 단, 예외가 발생하면 꼬이지만 항목8에서 해결할 예정
  3. 예제(스마트 포인터)
void f(){
   std::unique_ptr<Investment> pInv(createInvesment());
   //책에서는 auto_ptr로 예제를 들었으나, C++11에서 unique_ptr로 대체됨
}

3. 스마트 포인터

  1. unique_ptr

    • 자원에 대해 유일한 소유권을 가짐
    • unique_ptr을 복사 또는 대입하면, 기존 unique_ptr은 null로 변경됨
std::unique_ptr<Invesment> pInv1(createInvesment());
std::unique_ptr<Invesment> pInv2(pInv1);  // pInv1 = null로 할당됨
pInv1 = pInv2;  // pInv2 는 null로 할당됨
  1. shared_ptr
    • 참조 카운팅 방식 스마트 포인터(RCSP:Reference-Counting Smart Pointer)
    • 자원을 가리키는 외부 객체 개수를 확인하다가 0이 되면 자원을 자동으로 삭제함
std::shared_ptr<Investment> pInv1(createInvestment());
std::shared_ptr<Investment> pInv2(pInv1); // pInv1과 pInv2는 같은 객체를 가리킴
pInv1 = pInv2; // 위와 동일ㅈ

참고

  1. Effective C++

별첨

  1. RAII(Resource Acquisition Is Initialization)
    • 생성자에서 리소스를 획득하고 해당 소멸자에서 해제하는 것!
    • 주로 포인터, 뮤텍스 등의 자원의 소멸이 누락되어 발생되는 문제를 방지하기 위해 별도의 자원 관리 객체를 두고, 객체 생성시 자원 획득 및 소멸시 자원 해제하는 것을 얘기함
  2. RAII 클래스 예시
    • string, vector, thread, unique_ptr, shared_ptr, lock_guard, unique_lock, shared_lock
반응형
반응형

1. 스마트 포인터란?

  1. 스택의 포인터 변수가 없어질 때 자동 메모리 해제
  2. 종류
    • unique_ptr
    • shared_ptr
    • weak_ptr

2.unique_ptr

  1. exclusive ownership
    • 한 오브젝트에 하나의 포인터만 허용함
  2. 생성 방법
unique_ptr<Cat> catPtr = make_unique<Cat>()
  1. get 함수 또는 연산자를 통해 자원 접근 가능
//operator -> 를 이용해 자원 접근
catPtr->doSomething();

// get함수를 통해 자원 접근
catPtr.get() //포인터를 반환
  1. std::move를 통해 소유권 이전 가능
    • 소유권을 이전한 unique_ptr을 댕글링 포인터(dangling pointer) 라고 함
unique_ptr<Cat> catPtr1 = move(CatPtr)
  1. unique_ptr을 함수 인자로 전달하기
    • unique_ptr은 복사를 수행하지 않기 때문에 함수 인자로 전달 불가
    • unique_ptr에서 관리하는 자원을 직접 전달
void do_something(Cat* ptr) { };
do_something(catPtr.get());
  1. unique_ptr을 원소로 가지는 컨테이너에서 사용 방법
std::vector<std::unique_ptr<A>> vec;
std::unique_ptr<A> pa(new A(1));

//1. 복사 생성자가 없기 때문에 오류 발생!
vec.push_back(pa);

//2. 소유권을 이전하면서 vector에 추가
vec.push_back(std::move(pa));

//3. emplace_back 사용 시 생성하면서 컨테이너에 넣을 수 있음
vec.emplace_back(new A(1));
  1. 주로 멤버 변수가 포인터일 경우 사용

3. shared_ptr

  1. Shared ownership
    • 한 오브젝트에 여러 포인터 가능함
  2. Reference count를 계산하여, 아무도 가리키지 않을 때 자동으로 메모리 해제
  3. 생성 방법
shared_ptr<Cat> catPtr = make_shared<Cat>()
  1. 자원을 할당 시 주의 사항
    • 자원을 먼저 할당하고 shared_ptr 생성자 인자로 주소값을 전달하지 않도록 하자!
A* a = new A();
// 2개의 shared_ptr 객체가 각각 따로 제어블록을 생성함
// 참조계수가 각각 1을 가짐
std::shared_ptr<A> pa1(a);
std::shared_ptr<A> pa2(a);
  1. 클래스에서 this를 사용해 shared_ptr을 만들 때
    • 클래스에서 enable_shared_from_this<A> 를 상속 받음
    • shared _from _this()를 통해 this를 shared_ptr로 만들자!
class A : public std::enable_shared_from_this<A>{
   std::shared_ptr<A> get_shared_ptr() {
      return shared_from_this();
   }
}
  1. 원형 참조가 있으면 여전히 메모리 릭 발생 가능
    • weak_ptr을 통해 해결 가능
class Cat{
public:
   std::shared_ptr<Cat> mFriend;
};

int main(){
   std::shared_ptr<Cat> pKitty = std::make_shared<Cat>();
   std::shared_ptr<Cat> pNabi = std::make_shared<Cat>();

   pKitty->mFriend = pNabi;
   pNabi->mFriend = pKitty;
}

4. weak_ptr

  1. 일반 포인터와 shared_ptr 사이에 위치한 스마트 포인터
  2. 참조하여도 reference count가 증가되지 않음
  3. weak_ptr 자체로는 원래 객체를 참조할 수 없음
    • weak _ptr 생성시 shared _ptr 또는 weak _ptr을 생성자 인자로 받음
    • 일반 포인터 주소값으로 weak_ptr 생성 불가
    • 가리키는 객체가 이미 소멸되었으면, 빈 shared_ptr로 변환
    • 아닐 경우 해당 객체를 가리키는 shared_ptr로 변환
  4. lock 함수를 통해 shared_ptr로 변환
std::weak_ptr<A> other(std::shared_ptr<A>(new A(1)));
std::shared_ptr<A> o = other.lock();

참고

반응형

'C++ > C++' 카테고리의 다른 글

[C++] 함수형 프로그래밍  (0) 2021.03.11
[C++]병렬 프로그래밍  (0) 2021.03.11
[C++]LR value 및 최적화  (0) 2021.03.09
[C++]빌드 프로세스  (0) 2021.03.09
[C++]메모리 구조  (0) 2021.03.09

+ Recent posts

반응형