# include <iostream>
class A {
int data;
public:
A(int data) : data(data) { std::cout << " Constructor " << "\n"; }
A(const A& a) :data(a.data) {
std::cout << " Copy_Constructor " << "\n";
}
};
int main() {
A a(1);
A b(a);
A c(A(2));
}
다음과 같은 코드가 있다고 할 때, 원래대로 생각해보면
A(2)를 통해 일반생성자 객체가 생성되고, 그 객체를 A c()가 받아서 복사생성자가 생성될것만 같다.
하지만 실제로 컴파일해보면
Constructor
Copy_Constructor
Constructor
같은 결과가 나온다. 이는 컴파일러가 스스로 판단하여 '어차피 A(2)로 c를 만들거면 그냥 c를 A(2)로 처리하자' 라는 프로세스를 거쳐서 일반생성자 호출이 된 것이다.
이처럼 복사를 생략하는 작업을 복사생략이라 부른다.
반대로 복사생성자가 필요한 경우에서 안쓰이는 경우도 생긴다.
예전에 만든 Mystring에서 두 문자열을 합쳐서 출력하는
str3 = str1 + str2을 본적이 있을것이다.
이때, str3 은 str1과 str2로 만들것이기 때문에 굳이 복사생성자를 호출 할 필요가 없다. 하지만 컴파일러는 이를 실행하지 않았다.
이러한 문제를 해결하기 위해 나온것이 lv rv이다.
int a = 3;
이라는 코드가 있다고 하자. 이때 a는 주소값을 가진 변수이고 메모리상에 존재한다. 이를 lvalue라고 부른다. (lvalue라고 오른쪽에만 올수 있는게 아니다.)
이제 3을 보면, 3은 우리가 주소값을 가질 수 없다. lvalue에 3을 넣어주고나서 다시 사라진다. 이 값은 왼쪽에 올 수 없다
int a = 3;
int b = a;
// 이건 가능하지만
4 = int a;
// 이건 안된다!
지금까지 주구장창 써오던 레퍼런스는 좌측값에만 쓸 수 있다.
int a;
int& l_a = a;
int& r_b = 3;
여기서 마지막 코드는 오류가 난다는것이다. 3은 주소가 없으니 레퍼런스(다른 의미로는 주소값)을 가질수 없는게 당연하다.
int& func1(int& a) { return a; }
int func2(int b) { return b; }
int main() {
int a = 3;
func1(a) = 4;
std::cout << &func1(a) << "\n";
int b = 2;
a = func2(b); // 1번
func2(b) = 5; // 2번
std::cout << &func2(b) << "\n"; // 3번
}
이러한 코드가 있을때, 1번이 된다는것은 누구나 알거고,
2번 3번은 오류가 발생한다.
2번은 사실 말이 안된다. func2b)의 반환값은 int b, 즉 우측값을 반환하기 때문이다. (b가 좌측값이 아니다!)
3번도 마찬가지로 좌측값의 레퍼런스 문제로 작동하지 않는다.
반면, func1은 레퍼런스를 함수로 받는 경우이기 때문에 가능하다.
이동을 할 떄는 어떻게 해야할까? 소멸자에서 임시생성된 객체(string_content)가 소멸하지 않도록 nullptr로 바꿔준다.
이동생성자가 사용되는 궁극적인 목표는 성능향상이다. 기존의 복사생성으로는 너무 많은 rvalue 복사가 일어나기 때문이다. 따라서 ravlue 참조라는 기능을 이용해서 rvalue의 값을 이동(말그대로 이동이라 shallow copy)을 시켜주는거다.
이 오른쪽값 참조는 &&로 표기한다.
double avgerage() {
//...some code
returm avg;
}
int main() {
int num = 10;
int &&rnum = num // 1.
int &&rnum1 = 10; // 2.
double &&ravg = average(); // 3.
}
다음과 같은 코드가 있다고 할 때, 1번은 오류, 2, 3 번은 정상작동이다.
1번은 보이는것처럼 num이 lvalue이기떄문에 오류가 발생하는것이고 나머지 2번 3번은 lvalue만 보기때문에 정상작동한다.
이동생성자는 다음과 같이 생겼다.
MyString::MyString(MyString&& other){
// ...
}
이동생성자는 다른 개체맴버의 소유권을 가지고 오며, 복사생성자와 다르게 메모리 할당의 과정이 없다. 따라서 복사생성자보다 빠르며 메모리 공간이 절약된다.
얕은 복사와 비슷하다.
우측값만이 아니라 좌측값도 이동하고싶다면 어떻게 해야할까?
swap 함수를 생각해보면
template <typename T>
void my_swap(T &a, T &b) {
T tmp(a);
a = b;
b = tmp;
}
이렇게 temp 임시 변수를 만들어서 해결해도 되지만, 이는 쓸데없는 복사가 3번이나 이루어지게 된다.
이를 깔끔하게 해결하기 위해 나온것이 utility.h의 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));
}
여기서 move는 lvalue였던 a를 rvalue로 바꿔주는 역할을 한다.
최근에는 STL 컨테이너에도 이동생성과 이동대입이 생겨서 이걸 다 구현할 필요가 없다.