#include <iostream>
class A {
int data_;
public:
A(int data) : data_(data) { std::cout << "일반 생성자 호출!" << std::endl; }
A(const A& a) : data_(a.data_) {
std::cout << "복사 생성자 호출!" << std::endl;
}
};
int main() {
A a(1); // 일반 생성자 호출
A b(a); // 복사 생성자 호출
A c(A(2)); // ❓ 일반 생성자 호출
}
위 코드에서, A c(A(2)); // ❓ 일반 생성자 호출
이 부분의
cpp에서는 복사 생성을 굳이 수행하지 않고, 임시로 만들어진 A(2)를 그냥 2로 취급해 일반 생성자만 호출하게 된다.
좌측값
(lvalue): 주소값을 취할 수 있는 값(식의 왼오 모두 올 수 있음)우측값
(rvalue): 주소값을 취할 수 없는 값(실체가 없는 값)& 하나를 이용해서 정의하는 레퍼런스
좌측값 레퍼런스 자체도 좌측값이 됨
int& func1(int& a) { return a; }
int func2(int b) { return b; }
int main() {
int a = 3;
func1(a) = 4;
std::cout << &func1(a) << std::endl;
int b = 2;
a = func2(b); // 가능
func2(b) = 5; // 오류 1
std::cout << &func2(b) << std::endl; // 오류 2
}
func2(b)는 우측값, func1(a)는 좌측값에 해당하게 됨.
이동 생성자를 이용하면 기존의 메모리에 원소들이 모두 이동됨.
따라서 똑같은 내용으로 메모리를 한번만 차지함(복사 생성자는 두번 차지).
우측값 레퍼런스를 사용한 이동 생성자의 정의 부분은 다음과 같다.
MyString::MyString(MyString&& str) {
std::cout << "이동 생성자 호출 !" << std::endl;
string_length = str.string_length;
string_content = str.string_content;
memory_capacity = str.memory_capacity;
// 임시 객체 소멸 시에 메모리를 해제하지
// 못하게 한다.
str.string_content = nullptr;
}
우측값의 레퍼런스를 정의하기 위해서 좌측값과는 달리 &를 2개 사용해서 정의해야 함.
여기서 위 생성자의 경우 MyString 타입의 우측값을 인자로 받음.
str은 "타입이 <MyString의 우측값 레퍼런스>인 좌측값"이라고 보면 됨.
-> 표현식의 좌측에 올 수도 있음.
int a;
int& l_a = a;
int& ll_a = 3; // 불가능
int&& r_b = 3;
int&& rr_b = a; // 불가능
이동 생성 과정에서 예외가 발생하는 경우, 기존의 메모리에 원소들이 모두 이동되어 사라져서 새로 할당한 메모리를 섯불리 해제할 수 없음.
-> vector 및 cpp의 다른 컨테이너들은 이동 생성자가 noexcept
가 아닌 이상 이동 생성자를 사용하지 않음.
=> noexcept
추가하면 이동 생성자 사용.
MyString::MyString(MyString &&str) noexcept {
std::cout << "이동 생성자 호출 !" << std::endl;
string_length = str.string_length;
string_content = str.string_content;
memory_capacity = str.memory_capacity;
// 임시 객체 소멸 시에 메모리를 해제하지
// 못하게 한다.
str.string_content = nullptr;
}
좌측값이 우측값으로 취급될 수 있게 바꿔주는 함수: move 함수
#include <iostream>
#include <utility>
class A {
public:
A() { std::cout << "일반 생성자 호출!" << std::endl; }
A(const A& a) { std::cout << "복사 생성자 호출!" << std::endl; }
A(A&& a) { std::cout << "이동 생성자 호출!" << std::endl; }
};
int main() {
A a; // 일반
std::cout << "---------" << std::endl;
A b(a); // 복사
std::cout << "---------" << std::endl;
A c(std::move(a)); // 이동
}
std::move 함수가 인자로 받은 객체를 우측값으로 변환해서 리턴해 줌.
실제로는 단순한 타입 변환만 수행함(즉 우측값을 받는 함수들이 오버로딩되며 수행되는 것).
std::move는 이동은 수행하지 않음!
#include <iostream>
#include <vector>
template <typename T>
void wrapper(T u) {
g(u);
}
class A {};
void g(A& a) { std::cout << "좌측값 레퍼런스 호출" << std::endl; }
void g(const A& a) { std::cout << "좌측값 상수 레퍼런스 호출" << std::endl; }
void g(A&& a) { std::cout << "우측값 레퍼런스 호출" << std::endl; }
int main() {
A a;
const A ca;
std::cout << "원본 --------" << std::endl;
g(a);
g(ca);
g(A());
std::cout << "Wrapper -----" << std::endl;
wrapper(a);
wrapper(ca);
wrapper(A());
}
/*
[출력]
원본 --------
좌측값 레퍼런스 호출
좌측값 상수 레퍼런스 호출
우측값 레퍼런스 호출
Wrapper -----
좌측값 레퍼런스 호출
좌측값 레퍼런스 호출
좌측값 레퍼런스 호출
*/
위와 같이 출력된 이유는 C++ 컴파일러가 템플릿 타입을 추론할 때, 템플릿 인자 R가 레퍼런스가 아닌 일반적인 타입이라면 const를 무시하기 때문이다.
-> 즉, T가 전부 다 class A로 추론됨
따라서 아래와 같이 모든 조합의 템플릿 함수들을 정의해 주면 된다.
template <typename T>
void wrapper(T& u, T& v) {
g(u, v);
}
template <typename T>
void wrapper(const T& u, T& v) {
g(u, v);
}
template <typename T>
void wrapper(T& u, const T& v) {
g(u, v);
}
template <typename T>
void wrapper(const T& u, const T& v) {
g(u, v);
}
forward 키워드를 사용하면 위에서 거쳤던 복잡한 오버로딩(모든 조합에 대한 오버로딩)을 생략할 수 있음.
#include <iostream>
template <typename T>
void wrapper(T&& u) {
g(std::forward<T>(u));
}
class A {};
void g(A& a) { std::cout << "좌측값 레퍼런스 호출" << std::endl; }
void g(const A& a) { std::cout << "좌측값 상수 레퍼런스 호출" << std::endl; }
void g(A&& a) { std::cout << "우측값 레퍼런스 호출" << std::endl; }
int main() {
A a;
const A ca;
std::cout << "원본 --------" << std::endl;
g(a);
g(ca);
g(A());
std::cout << "Wrapper -----" << std::endl;
wrapper(a);
wrapper(ca);
wrapper(A());
}
#include <iostream>
void show_value(int&& t) {
std::cout << "우측값 : " << t << std::endl;
}
int main() {
show_value(5); // 우측값 ok!
int x = 3;
show_value(x); // 애러
}
이때 int&& t
는 우측값 외에는 못 받음.
보편적 레퍼런스(Universal reference): 템플릿 인자 T 에 대해서, 우측값 레퍼런스를 받는 형태