article thumbnail image
Published 2020. 11. 13. 22:47

1. 원시 포인터를 대체할 수 있는 좋은 수단은 유니크 포인터이다.

 

2. 유니크 포인터는 오토 포인터와 달리, 복사 생성자, 복사 할당 연산, 이동 생성자, 이동 할당을 지원하지 않기 때문에, 오류 발생 위험이 훨씬 적다.

 

3. 오토 포인터 대신 유니크 포인터를 사용한다.

 

4. 값을 할당할때는 make_unique 메소드를 사용하는 것이 오류 위험이 가장 적고, 퍼펙트 포워딩을 지원한다.

 

#include <memory>

using namespace std;

auto main() -> int {
	//auto sp = unique_ptr<int>{ nullptr };
	//auto sp = unique_ptr<int>{ new int {123} };
	auto sp = make_unique<int>(123); //추천하는 할당벙
}

 

5. 명시적으로 생성자를 구현하지 않는한, make_unique는 객체를 생성하지 않는다.

 

#include <memory>
using namespace std;

struct Hen
{
	unsigned id;
	float eggs;

	//Hen(unsigned _id, float _eggs) : id{_id}, eggs{_eggs} {}
};

auto main() -> int {
	auto hen = make_unique<Hen>(1, 5.6f);

}

 

6. 유니크 포인터는 자체적으로 이동연산을 허용하지 않기 때문에, 객체에 대한 소유권을 이전할 때는  std::move 메소드를 사용한다.

 

auto main() -> int {
	auto hen = make_unique<Hen>(1, 5.6f);
	
	//auto hen2 = hen; 오류
	auto hen2 = move(hen);

 

7. 객체의 복사가 일어나지 않기 때문에, 소멸자 또한 한번만 작동한다는 것을 명심한다.

 

#include  "Precompiled.h"
#include <memory>
using namespace std;

struct Hen
{
	unsigned id;
	float eggs;

	Hen(unsigned _id, float _eggs) : id{_id}, eggs{_eggs} {}
	~Hen() { TRACE(L"Fried Chicken!!\n"); }
};

auto main() -> int {
	auto hen = make_unique<Hen>(1, 5.6f);
	
	if (hen) { TRACE(L"New Born Chick.\n"); }

	auto hen2 = move(hen);
	if (hen) { TRACE(L"Hen 1.\n"); }
	if (hen2) { TRACE(L"Hen 2.\n"); }
}

 

8. 복사 생성이 일어나지 않는다는 점을 주의한다.

 

#include  "Precompiled.h"
#include <memory>
#include <iostream>
using namespace std;

struct Hen
{
	unsigned id;
	float eggs;

	Hen(unsigned _id, float _eggs) : id{ _id }, eggs{ _eggs } { TRACE(L"Born"); }
	~Hen() { TRACE(L"Fried Chicken!!\n"); }
	void cry() {
		cout << id << endl;
	}

};

auto main() -> int {
	auto hen = make_unique<Hen>(1, 5.6f);

	Hen copy = *hen;		// 유니크 포인터는 복사 연산을 하지 않기 때문에 copy에서  새로운 객체는 		
	Hen* ptr1 = hen.get();	// 세 코드는 모두 레퍼런스처럼 동작하며
	Hen& ref = *hen;		// 모두 같은 주소를 갖게된다.

	copy.cry();
	ptr1->cry();
	ref.cry();

	Hen* ptr2 = hen.release(); // hen의 소유권이 ptr2로 이전되면서, 새로운 주소를 할당받지는 않는다. 
							   // 새로운 객체가 생성되는 것은 아니기 때문에, 생성자가 호출되지 않는다.
							   // 소유권만 이동했을뿐, 세 변수는 이전과 갖은 주소를 갖게되며
							   // ptr2도 갖은 주소를 갖는다. 그러나, hen은 empty 값을 가진다.
	
	//hen->cry();				   // hen은 empty을 가지기 때문에 오류

	hen.reset(ptr2);		   // ptr2로 부터 다시 객체의 소유권을 가져온다. 

	copy.cry();				   
	ptr1->cry();			   // 처음부터 끝까지 같은 주소를 갖는다는 사실을 명심한다.
	ref.cry();				   
}

 

9. 함수의 매개변수로 들어가도 객체 생성을 거부하며, 함수의 스코프를 벗어나도 동일한 주소를 가진다.

 

#include  "Precompiled.h"
#include <memory>
#include <iostream>
using namespace std;

struct Hen
{
	unsigned id;
	float eggs;

	Hen(unsigned _id, float _eggs) : id{ _id }, eggs{ _eggs } { TRACE(L"Born"); }
	~Hen() { TRACE(L"Fried Chicken!!\n"); }
};

auto GetHen() -> unique_ptr<Hen> {
	return make_unique<Hen>(1,0);
}

auto UpdateHen_1(unique_ptr<Hen> hen)->void {
	hen->eggs += 13.0f;
}

auto UpdateHen_2(unique_ptr<Hen>& hen)->void {
	hen->eggs += 13.0f;
}

auto UpdateHen_3(unique_ptr<Hen> hen)->unique_ptr<Hen> {
	hen->eggs += 13.0f;
	return hen;
}


auto main() -> int {
	auto Chicken = GetHen();
	//UpdateHen_1(Chicken);  //오류
	UpdateHen_2(Chicken);
	Chicken = UpdateHen_3(move(Chicken));
	cout << Chicken->eggs;
}

 

7. 이미 정의가 되어있는 클래스 객체에 대해서, 내부 코드를 수정하지 않고, 커스텀 딜리터를 정의할 수 있다.

 

#include "Precompiled.h"
#include <memory>
using namespace std;

struct operation_deleter {
	typedef PTP_WORK pointer;
	auto operator()(pointer value) const throw() -> void {
		CloseThreadpoolWork(value);
	}
};

auto main() -> int {
	typedef unique_ptr<PTP_WORK, operation_deleter> operation;
	auto op = operation{
		CreateThreadpoolWork(
			[](PTP_CALLBACK_INSTANCE, void*,PTP_WORK) {
				TRACE(L"Hello From Thread Pool\n");
			},
			nullptr,nullptr)
	};

	if (op) {
		SubmitThreadpoolWork(op.get());
		WaitForThreadpoolWorkCallbacks(op.get(), false);
		//CloseThreadpoolWork(op.get());	//스코프를 벗어나면 자동으로 딜리터를 작동시킨다.
	}
}

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

스마트 클래스  (0) 2020.11.15
클래스 구현 시, 생각해야 할 것  (0) 2020.11.14
윈도우 HANDLE 과 COM 인터페이스에 대해서.  (0) 2020.11.14
쉐어드 포인터  (0) 2020.11.14
Assertion  (0) 2020.11.13
복사했습니다!