현대적인 C++에서는 직접적인 포인터를 사용하지 않고 스마트 포인터를 이용하여 간접적으로 활용하는게 일반적이다.

언리얼 엔진만 보더라도 전부 스마트 포인터로 되어있다.

 

스마트 포인터 : 포인터를 알맞는 정책에 따라 관리하는 객체 (포인터를 래핑해서 사용)

 

shared_ptr

레퍼런스 카운트를 통해 레퍼런스 카운트가 0일때만 해제된다.

명시적으로 delete를 해줄 필요 없이 특정 조건(레퍼런스 카운트 == 0)을 만족하면 알아서 delete를 해준다.

make_shared<>()를 통해 초기화 시켜주는게 성능면에서 조금 이점이 있다고 한다. (메모리 블럭을 한번에 만들어준다는 차이가 있음)

간단하게 구현한 코드

더보기
template<typename T>
class SharedPtr
{
public:
	SharedPtr() {}
	SharedPtr(T* ptr) : _ptr(ptr)
	{
		if (_ptr != nullptr)
		{
			_block = new RefCountBlock();
			cout << "RefCount : " << _block->_refCount << endl;
		}
	}

	SharedPtr(const SharedPtr& sptr) : _ptr(sptr._ptr), _block(sptr._block)
	{
		if (_ptr != nullptr)
		{
			_block->_refCount++;
			cout << "RefCount : " << _block->_refCount << endl;
		}
	}

	void operator=(const SharedPtr& sptr)
	{
		_ptr = sptr._ptr;
		_block = sptr._block;

		if (_ptr != nullptr)
		{
			_block->_refCount++;
			cout << "RefCount : " << _block->_refCount << endl;
		}
	}

	~SharedPtr()
	{
		if (_ptr != nullptr)
		{
			_block->_refCount--;
			cout << "RefCount : " << _block->_refCount << endl;
		}

		if (_block->_refCount == 0)
		{
			delete _ptr;
			delete _block;
			cout << "Delete Data" << endl;
		}
	}

public:
	T* _ptr = nullptr;
	RefCountBlock* _block = nullptr;
};

 

사이클이 발생하는 경우 소멸이 되지 않는 문제가 있다.

사이클이 발생하지 않는지 잘 확인해서 사이클을 끊어주어야 한다.

 

 

weak_ptr

ref count(객체를 참고하는게 몇개인지)와 더불어 weak count(weak_ptr이 몇개가 참조 중인지)가 같이 관리된다.

shared_ptr과 다르게 객체 삭제 시점에 ref count block은 삭제하지 않고 냅둔다.

또한 shared_ptr처럼 바로 사용할수는 없고 expired()로 삭제 여부를 검사 후 lock()을 통해 shared_ptr를 반환 받아 사용해야 한다.

weak_ptr<Knight> _target;
if (_target) // 오류! shared_ptr처럼 사용 불가능
{
	// ..
}

// ------------

weak_ptr<Knight> _target;
if (_target.expired() == false)
{
	shared_ptr<Knight> sptr = _target.lock();
    // ..
}

 

장점은 생명 주기에서 자유로워진다. weak_ptr 자체는 객체의 소멸 자체에는 영향을 주지 않기때문에 순환 구조가 일어날 수 없다.

단점은 expired()로 명시적으로 체크 후 shared_ptr로 다시 한번 전환하는 과정이 추가되기 때문에 번거로워진다.

상황에 맞춰서 shared_ptr만 사용할 것인지, weak_ptr도 병행해서 사용할 것인지 잘 고르면 된다.

 

 

unique_ptr

단 하나만 존재할 수 있다.

다른 곳에 넘겨줄때는 move를 통해 모두 이양해서 사용해야 한다.

일반적인 복사가 막히고 이동만 가능한 포인터라고 이해하면 된다.

make_unique<>()를 통해 초기화 하면 된다.

 

쌩으로 포인터를 사용하는 것은 더 이상 없다고 생각하면 된다.

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

[Modern C++] 람다 (lambda)  (0) 2022.08.31
[Modern C++] 전달 참조(forwarding reference)  (0) 2022.08.31
[Modern C++] 오른값 참조 (rvalue reference)  (0) 2022.08.31
[Modern C++] #2  (0) 2022.08.31
[Modern C++] #1  (0) 2022.08.31

한줄 요약 : 함수 객체를 빠르게 만드는 문법. 일종의 익명 함수이다.

람다 자체로 C++11에 새로운 기능이 들어간 것은 아니다.

 

// 기본형태 : [캡처](매개변수)->리턴값{함수내용;};
// 리턴값은 생략 가능
[](Item& item) { return item._rarity == Rarity::UNIQUE; };

 

// 람다에 의해 만들어진 실행시점 객체 = 클로저(closure)
auto isUniqueLambda = [](Item& item) { return item._rarity == Rarity::UNIQUE; };

만들어진 람다 객체는 클로저(closure)라고 한다.

 

 

// 값 복사 방식
int itemId = 4;
auto findByItemIdLambda = [=](Item& item) // 기본 캡처 모드
{
	return item._itemId == itemId; // itemId = 4
};
itemId = 10;

// -----

// 참조 방식
int itemId = 4;
auto findByItemIdLambda = [&](Item& item) // 기본 캡처 모드
{
	return item._itemId == itemId; // itemId = 10
};
itemId = 10;

[  ] 캡처 : 함수 객체 내부에 변수를 저장하는 개념과 유사하다. 값 복사 방식(=), 참조 방식(&)

 

int itemId = 4;
Rarity rarity = Rarity::UNIQUE;
ItemType type = ItemType::WEAPON;
auto findByItem = [&itemId, rarity, type](Item& item)
{
	return item._itemId == itemId && item._rarity == rarity && item._type == type;
};

변수마다 캡처 모드를 지정해서 사용할 수도 있다.

 

int itemId = 4;
Rarity rarity = Rarity::UNIQUE;
ItemType type = ItemType::WEAPON;
auto findByItem = [=, &type](Item& item) // 기본은 값 복사, type만 참조 방식 지정
{
	return item._itemId == itemId && item._rarity == rarity && item._type == type;
};

기본 캡처 모드에 추가로 특정 변수만 캡처 모드를 지정할 수도 있다.

 

하지만 C++에서는 기본 캡처 모드 사용을 지양하라고 권고한다.

원래는 보편 참조(universal reference)였으나 C++17로 넘어오며 명칭이 변경 되었다.

활용할 일이 많지는 않다고 한다.

 

&&는 무조건 오른값 참조인가? -> X

template, auto같은 형식 연역(type deduction)이 발생할 때만 등장한다.

 

왼값 참조로 전달하면 왼값 참조, 오른값 참조로 전달하면 오른값 참조로 동작한다.

단, const 같은 키워드가 붙으면 오른값 참조로만 작동한다.

template<typename T>
void Test_ForwardingRef(T&& param) // 전달 참조
// ----
template<typename T>
void Test_ForwardingRef(const T&& param) // 오른값 참조만 가능

 

오른값 참조에 관한 추가사항

 

void TestRvalueRef(Knight&& knight) {}

Knight k1;
Knight&& k2 = k1;
Test_RValueRef(k2); // 오류!

 

k2는 오른값 참조 타입임에도 불구하고 정작 함수에는 넘겨줄수 없다.

그 이유는 오른값 참조 타입은 맞지만 k2자체는 왼값이기 때문이다.

왼값과 오른값의 정의를 잘 생각해보면 무리없이 이해할 수 있다. (왼값: 단일식을 벗어나도 유지되는 값)

 

void TestRvalueRef(Knight&& knight) {}

Knight k1;
Knight&& k2 = k1;
Test_RValueRef(move(k2)); // OK!

 

또한, 함수 호출시에 move로 오른값 참조로 만들어서 넘겨줘도 매개변수로 사용할 때 다시 왼값이 되어버린다.

 

인자로 왼값 참조, 오른값 참조를 넘겨주면 무조건 왼값 참조로 변경된다.

해당 함수 내에서 전달 받았던 그대로 왼값 참조, 오른값 참조로 다른 함수를 호출하려면 forward를 이용하면 된다.

 

class Knight
{
public:
	Knight() { cout << "기본 생성자" << endl; }
	Knight(const Knight&) { cout << "복사 생성자" << endl; }
	Knight(Knight&&) noexcept { cout << "이동 생성자" << endl; }
};

void Test_Copy(Knight knight)
{
	// do something
}

template<typename T>
void Test_ForwardingRef(T&& param) // 전달 참조
{
	Test_Copy(forward<T>(param));
}

int main()
{
	Knight k1; // 기본 생성자

	Test_ForwardingRef(k1); // 복사 생성자
	Test_ForwardingRef(move(k1)); // 이동 생성자

	return 0;
}

 

앞으로 전달 참조는 무조건 forward로 받아주면 된다.

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

[Modern C++] 스마트 포인터 (smart pointer) -完-  (0) 2022.09.01
[Modern C++] 람다 (lambda)  (0) 2022.08.31
[Modern C++] 오른값 참조 (rvalue reference)  (0) 2022.08.31
[Modern C++] #2  (0) 2022.08.31
[Modern C++] #1  (0) 2022.08.31

C++11에서 가장 핵심적인 변화.

 

lvalue : 단일식을 넘어서 계속 지속되는 개체

rvalue : lvalue가 아닌 나머지. (임시 값, 열거형, 람다, i++ 등)

 

void TestKnight_Copy(Knight knight) {} // 값 복사
void TestKnight_LValueRef(Knight& knight) {} // 참조
void TestKnight_ConstLValueRef(const Knight& knight) {} // 임시 객체도 넘겨줄 수 있음

TestKnight_ConstLValueRef(Knight()); // 임시 객체도 넘겨줄 수 있음

기존의 문법들

 

void TestKnight_RValueRef(Knight&& knight) {}

Knight k1;
TestKnight_RValueRef(k1) // 오류!

오른값 참조를 받을때는 인자로 && 를 받으면 되는데 왼값은 인자로 받을 수 없다.

static_cast<Knight&&>를 하면 쓸수 있기는 하다.

 

그럼 오른값 참조를 하는 이유는?

원본을 더 이상 사용하지 않을 것이라는 힌트를 제공해준다.

기존의 복사 생성자는 얕은 복사를 우려해서 깊은 복사를 구현했는데, 복사 대상의 객체 크기가 클수록 복사 비용이 커지는 문제가 있다.

하지만 오른값 참조로 받은 값은 인자로 넘겨준 시점에서 더이상 쓰지 않겠다고 알려준거나 다름없기 때문에 값을 마음대로 수정해도 상관이 없으므로 얕은 복사로 데이터를 모두 이동시켜도 무방하다. (이동의 개념)

class Knight
{
public:
	// 이동 생성자
    Knight(Knight&& knight)
    {
        // do something
    }

	// 이동 대입 연산자
    void operator=(Knight&& knight)
    {
        _hp = knight._hp;
        _pet = knight._pet;
        knight._pet = nullptr;
    }
}

 

 

std::move

오른값 참조로 캐스팅을 한 것과 같다.

이름이 move라서 무언가 옮겨줄 것 같지만 lvalue를 rvalue로 캐스팅 해주는 것에 불과하다.

Knight k1;
Knight k2;

k1 = static_cast<Knight&&>(k2);
k1 = std::move(k2); // 위와 동일
// 여담으로 move의 이름 후보 중 하나가 rvalue_cast 였다고 함

 

차후 다룰 unique_ptr에서 활용할 때가 있다.

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

[Modern C++] 람다 (lambda)  (0) 2022.08.31
[Modern C++] 전달 참조(forwarding reference)  (0) 2022.08.31
[Modern C++] #2  (0) 2022.08.31
[Modern C++] #1  (0) 2022.08.31
[STL] algorithm  (0) 2022.08.31

[delete]

 

특정 함수의 사용을 명시적으로 막는다.

 

class Knight
{
private:
	void operator=(const Knight& k);
};

과거 사용하던 방식은 멤버 함수를 private으로 설정하고 정의를 안하는 방식이었는데, 이런 경우 빌드에서는 문제가 없으나 정의가 안되어 있으므로 해당 함수에 접근시 링크 단계에서 에러가 발생하게 된다.

그렇기 때문에 에러가 조기에 발견되지 않는 문제가 발생한다.

 

class Knight
{
public: // public으로 두는게 통상적인 관례
	void operator=(const Knight& k) = delete;
};

 이렇게 delete를 붙여주게되면 해당 함수에 접근하는 경우 빌드시 에러를 잡아줄 수 있다.

 

[override, final]

 

가상함수와 연관성이 있다.

 

class Creature
{
};

class Player : public Creature
{
public:
	virtual void Attack() {}
};

class Knight : public Player
{
public:
	virtual void Attack() {}
};

int main()
{
	Creature* player = new Knight();
	player->Attack(); // 사용 불가!
    
    delete player;
    return 0;
}

가상함수의 문제는 현재 내 클래스에서 최초로 정의한 함수인지, 혹은 부모 클래스에서 정의된 함수인지를 알 수 있는 방법이 없다.

 

class Player : public Creature
{
public:
	virtual void Attack() {}
};

class Knight : public Player
{
public:
	virtual void Attack() override {} // 오버라이딩을 명시
};

오버라이딩한 멤버 함수에 override 키워드를 붙여줌으로써 상속받은 가상 함수라는것을 명시적으로 표현할 수 있다.

실수 방지 및 가독성이 좋아지게 된다.

C#에서는 기본적인 문법이다.

 

 

final은 재정의를 막는 키워드이다.

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

[Modern C++] 전달 참조(forwarding reference)  (0) 2022.08.31
[Modern C++] 오른값 참조 (rvalue reference)  (0) 2022.08.31
[Modern C++] #1  (0) 2022.08.31
[STL] algorithm  (0) 2022.08.31
[STL] set, multimap, multiset  (0) 2022.08.31

+ Recent posts