Patrick's Devlog

[Modern C++] Smart Pointer 본문

Study/C++

[Modern C++] Smart Pointer

Patrick_ 2022. 5. 17. 17:58

개요

급히 Modern C++에 대해 공부해야할 일이 생겨 간단하게 인터넷을 통해서 숙지하고자 게시글을 정리한다. 자료는 TCP School에서 참고하여 정리하였으며, 좀 더 자세한 내용을 확인하고 싶으면 아래 참고 자료 링크에 들어가면 된다. 


스마트 포인터?

C++에서 new 키워드를 사용해 동적으로 할당받은 메모리는 반드시 delete를 사용해 해제해야 한다. C++에서는 메모리 누수로부터 프로그램 안전성을 보장하기 위해 스마트포인터를 제공한다.

스마트 포인터는 포인터처럼 동작하는 클래스 템플릿으로, 사용이 끝난 메모리를 자동으로 해제해준다. 

동작

보통 new 키워드를 이용해 원시 포인터가 실제 메모리를 가리키도록 초기화한 후 원시 포인터를 스마트 포인터에 대입하여 사용한다. 정의된 스마트 포인터의 수명이 다하면, 소멸자는 delete를 이용해 할당된 메모리를 해제해준다. 따라서 new 키워드가 반환하는 주소값을 스마트 포인터에 대입하면 메모리를 해제할 필요가 없어진다.

◆ 종류

C++11 이전에는 auto_ptr을 이용해 수행해왔지만 C++11 이후 auto_ptr은 사라지고 unique_ptr, shared_ptr, weak_ptr을 사용한다. 스마트 포인터는 memory 헤더파일에 정의되어 있다. 

 

unique_ptr

하나의 스마트 포인터만이 특정 객체를 소유할 수 있도록 객체에 소유권 개념을 도입한 스마트 포인터이다. 이 스마트 포인터는 해당 객체의 소유권을 지니고 있을 때만 소멸자가 해당 객체를 삭제할 수 있다. unique_ptr 인스턴스는 move() 멤버 함수를 통해 소유권을 이전할 수 있지만 복사할 수는 없다. 또한 소유권이 이전되면 전에 사용하던 unique_tr 인스턴스는 더이상 해당 객체를 소유하지 않게 재설정된다. 

unique_ptr<int> ptr01(new int(5)); // int형 unique_ptr 선언 및 초기화
auto ptr02 = move(ptr01); // ptr01에서 ptr02로 소유권 이전
// unique_ptr<int> ptr03 = ptr01; // 대입 연산자를 이용한 복사는 오류 발생
ptr02.reset(); // ptr02가 가리키고 있는 메모리 영역 삭제
ptr01.reset(); // ptr01이 가리키고 있는 메모리 영역 삭제

보통 C++ 객체에 대해 스마트 포인터가 필요한 상황에서 주로 unique_ptr을 사용하면 된다. 

 

make_unique()

C++14 이후부터 제공되는 make_unique() 함수를 사용하면 unique_ptr 인스턴스를 안전하게 생성할 수 있다. make_unique() 함수는 전달받은 인수를 사용해 지정된 타입의 객체를 생성하고, 생성된 객체를 가리키는 unique_ptr을 반환한다. 이 함수를 사용하면 예외 발생에 대해 안전하게 대처할 수 있다. 

 

아래의 예제는 Person 객체를 가리키는 hong이라는 unique_ptr를 make_unique() 함수를 통해 생성하는 예제이다. 

#include <iostream>
#include <memory>
using namespace std;

class Person {
private:
	string name_;
	int age_;

public:
	Person(const string& name, int age);
	~Person() { cout << "소멸자가 호출되었습니다." << endl; }
	void ShowPersonInfo();
};

int main(void) {
	unique_ptr<Person> hong = make_unique<Person>("길동", 29);
	hong->ShowPersonInfo();
	return 0;
}

Person::Person(const string& name, int age) { // 기초 클래스 생성자의 정의
	name_ = name;
	age_ = age;
	cout << "생성자가 호출되었습니다." << endl;
}

void Person::ShowPersonInfo() { cout << name_ << "의 나이는 " << age_ << "살입니다." << endl; }

위의 예제 실행 결과

Person을 가리키는 unique_ptr 인스턴스인 hong은 사용이 끝난 후 delete를 이용할 필요가 없다. 

 

shared_ptr

하나의 특정 객체를 참조하는 스마트 포인터가 총 몇개인지 참고하는 스마트 포인터이다. 이렇게 참조하고 있는 스마트 포인터의 개수를 참조 횟수(reference count)라고 한다. 참조 횟수는 특정 객체에 새로운 shared_ptr이 추가될 때마다 1씩 증가하며 수명이 다할때마다 1씩 감소한다. 수명이 다해 참조 횟수가 0이되면 delete 키워드를 사용해 메모리를 자동으로 해제한다.

shared_ptr<int> ptr01(new int(5)); // int형 shared_ptr 선언 및 초기화
cout << ptr01.use_count() << endl; // 1
auto ptr02(ptr01); // 복사 생성자를 이용한 초기화 
cout << ptr01.use_count() << endl; // 2
auto ptr03 = ptr01; // 대입을 통한 초기화 
cout << ptr01.use_count() << endl; // 3

use_count() 멤버 함수는 shared_ptr 객체가 현재 가리키고 있는 리소스를 참조중인 소유자의 수를 반환해준다.

 

make_shared()

위와 같은 방법 이외에도 make_shared()를 사용하면 shared_ptr 인스턴스를 안전하게 생성할 수 있다. make_shared() 함수는 전달받은 인수를 사용해 지정된 타입의 객체를 생성하고, 생성된 객체를 가리키는 shared_ptr을 반환한다. 이 함수를 사용하면, 예외 발생에 대해 안전하게 대처할 수 있다. 

#include <iostream>
#include <memory>
using namespace std;

class Person {
private:
	string name_;
	int age_;

public:
	Person(const string& name, int age);
	~Person() { cout << "소멸자가 호출되었습니다." << endl; }
	void ShowPersonInfo();
};

int main(void) {
	shared_ptr<Person> hong = make_shared<Person>("길동", 29);
	cout << "현재 소유자 수 : " << hong.use_count() << endl; // 1
	auto han = hong;
	cout << "현재 소유자 수 : " << hong.use_count() << endl; // 2
	han.reset(); // shared_ptr인 han을 해제함. 
	cout << "현재 소유자 수 : " << hong.use_count() << endl; // 1
	return 0;
}

Person::Person(const string& name, int age) { // 기초 클래스 생성자의 정의
	name_ = name;
	age_ = age;
	cout << "생성자가 호출되었습니다." << endl;
}

void Person::ShowPersonInfo() { cout << name_ << "의 나이는 " << age_ << "살입니다." << endl; }

위의 예제 실행 결과

위의 예제는 Person 객체를 가리키는 hong이라는 shared_ptr를 make_shared() 함수를 통해 생성하는 예제이다.

 

weak_ptr

하나 이상의 shared_ptr 인스턴스가 소유하는 객체에 대해 접근을 제공하지만, 소유자의 수는 포함되지 않는 스마트 포인터이다. 

 

shared_ptr는 참조 횟수를 기반으로 동작하는 스마트 포인터이며, 서로가 상대방을 가리키는 shared_ptr를 가지고 있다면 참조 횟수는 절대 0이 되지 않으므로 메모리는 영원히 해제되지 않는다. 이렇게 서로가 상대방을 참조하고 있는 상황을 순환 참조(circular reference)라고 한다. weak_ptrㅇ느 이러한 shared_ptr 인스턴스 사이 순환 참조를 제거하기 위해 사용된다. 


참고 문헌

http://www.tcpschool.com/cpp/cpp_template_smartPointer

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

 

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

[Effective C++] Chapter 2 정리 - 1  (0) 2022.05.26
[Modern C++] Lvalue 및 Rvalue  (0) 2022.05.20
[Modern C++] 시작하기  (0) 2022.05.16
[Effective C++] Chapter 1 정리 - 2  (0) 2022.05.11
[Effective C++] Chapter 1 정리 - 1  (0) 2022.05.09