원래는 보편 참조(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

C++11부터를 Modern C++로 분류한다.

 

[auto]

 

auto, template 같은 타입 추론을 형식 연역(type deduction) 이라고 한다.

 

주의사항으로 기본 auto는 const, reference를 무시하므로 명시적으로 붙여주어야만 한다.

int a = 3;
int& ref = a;
auto = ref // int&가 아닌 int형으로 잡힌다

 

일반적인 상황에서는 가독성을 위해 기존 타입을 사용하는게 나을수도 있다.

다만, 타입이 너무 긴 경우 (컨테이너의 pair, iterator 등)

 

[중괄호 초기화]

 

vector 등 컨테이너와 잘 어울린다.

축소 변환을 방지한다.

int x = 0;
double y = x; // y(x) 둘다 허용

double y{x}; // 컴파일 에러!

 

객체를 중괄호 초기화로 생성자를 불러오는 경우, initializer_list 생성자를 만들어주어야 한다.

class Knight
{
public:
	Knight(initializer_list<int> li)
    {
    	// do something
    }
};

 단, 이니셜라이저 리스트 생성자가 존재하면 중괄호 초기화 시 무조건 이니셜라이저 리스트 생성자만 호출이 된다.

 

괄호 초기화

- 전통적인 C++ (거부감이 없음)

- vector 등 특이 케이스에 대해서만 {} 사용

 

중괄호 초기화

- 초기화 문법의 일치화

- 축소 변환 방지

 

[nullptr]

 

오동작을 방지한다.

가독성이 좋아진다.

 

[using]

 

typedef을 대체할 수 있는 문법

typedef int id;
using id2 = int; // typedef과 순서가 반대

 

직관성이 좋아진다.

typedef void (*MyFunc)();
using MyFunc2 = void(*)();

 

typedef은 템플릿을 활용할 수 없지만 using은 가능하다.

 

[enum class]

 

scoped enum

 

이름공간 관리에 이점이 있고 암묵적인 변환이 금지된다.

 

enum Player { NONE, .. };
enum Monster { NONE, .. }; // 재정의 오류

기존 enum은 범위가 전역이기 때문에 중복된 이름을 사용할 수 없다.

 

enum class ObjectType1 { PLAYER, .. };
enum class ObejctType2 { PLAYER, .. };

ObjectType1::PLAYER;
ObjectType2::PLAYER;

enum class는 이름 공간으로 잡히기 때문에 중복된 이름 사용이 가능하다.

 

기존 enum은 암시적인 형변환이 가능했지만 enum class는 오로지 같은 enum class 타입끼리만 연산이 가능해진다.

장점이 될 수도 있고 단점이 될 수도 있다.

 

enum class ObjectType { PLAYER, MONSTER };

int t1 = ObjectType::PLAYER; // 컴파일 에러
int t2 = static_cast<int>(ObjectType::PLAYER); // 형 변환을 해야만 가능

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

[Modern C++] 오른값 참조 (rvalue reference)  (0) 2022.08.31
[Modern C++] #2  (0) 2022.08.31
[STL] algorithm  (0) 2022.08.31
[STL] set, multimap, multiset  (0) 2022.08.31
[STL] map  (0) 2022.08.31

자주 사용되는 것들

find, find_if

count, count_if

all_of, any_of, none_of

for_each

remove, remove_if

 

find: 찾으면 찾은 값의 iterator, 못찾으면 end를 반환한다.

 

find_if: 찾으려는 값 대신 함수 객체/함수 포인터를 인자로 넘겨준다. 함수 객체/함수 포인터가 조건이 되며 bool을 반환해 주는 형태여야 한다.

 

count: 찾은 값의 개수를 반환해준다.

 

count_if: 조건에 맞는 값 개수를 반환해준다.

 

셋 다 bool을 반환해준다.

 

all_of: 모든 데이터가 조건에 맞는가?

any_of: 조건에 맞는 데이터가 하나라도 있는가?

none_of: 모든 데이터가 조건에 맞지 않는가?

 

for_each: 범위 내의 모든 데이터에 대해 함수 객체/함수 포인터를 실행한다.

struct MultiplyBy3
{
    void operator()(int& n)
    {
        n = n * 3;
    }
};

for_each(v.begin(), v.end(), MultiplyBy3());

 

remove: 아래와 같다.

 

remove_if: 이름과는 달리 데이터를 실제로 지워주지는 않고 앞으로 땡겨와서 채워준다. 예를들어 값 6개중 3개가 조건에 맞았다면, 조건에 맞는 값 3개를 앞으로 땡겨오고 4번째부터는 방치가 된다.

다만 4번째 위치의 iterator가 반환이 되므로 해당 위치부터 끝까지 erase 해주면 의도하는 결과를 낼 수 있다.

auto it = remove_if(v.begin(), v.end(), IsOdd());
v.erase(it, v.end());

// 아래와 같이 한줄로 쓸수 있음
v.erase(remove_if(v.begin(), v.end(), IsOdd()), v.end());

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

[Modern C++] #2  (0) 2022.08.31
[Modern C++] #1  (0) 2022.08.31
[STL] set, multimap, multiset  (0) 2022.08.31
[STL] map  (0) 2022.08.31
[STL] deque  (0) 2022.08.31

+ Recent posts