위 블로그 중간에
일단
std::move(a) 가 우측값으로 바꾸는 것은 맞습니다. 그런데 문제는 a가 const A& 이므로, std::move(a)의 타입은
const A&& 가 된다는 것입니다. 그런데 A의 생성자에는 const A& 와 A&&두 개 밖에 없죠.
따라서 여기선 컴파일러가 const A& 를 택하게 됩니다. 따라서 복사 생성자가 호출이 되겠죠.
라는 말이 있다.
코드는 아래의 코드이다.
#include<iostream>
using namespace std;
class A {
public:
A() { std::cout << "ctor\n"; }
A(const A& a) { std::cout << "copy ctor\n"; }
A(A&& a) noexcept { std::cout << "move ctor\n"; }
};
class B {
public:
A a_;
/*B(A a) : a_(a)
{
cout << "여기 는 언제 ?" << endl;
}*/
B(const A& a) : a_(std::move(a))
{
cout << "복사 생성자 " << endl;
}
};
int main() {
A a;
B b(a);
return 0;
}
말 그대로 A의 생성자에 const A& a ( 복사 생성자 ) 가 있기 때문에 std::move(a) 로 우측값으로 변경했음에도 const A&& 가 되어 const 가 있는 복사생성자(const A& a ) 를 택했다는건데
그럼 B의 생성자 B(const A& a ) 에서 const 를 때면 이동 생성자(A&& a) 를 선택할까?
실행해보면 A(A&& a) 이동생성자를 선택한다.
그렇다면 okay
"const 가 문제였구나" 하고 const 를 때고 싶은데 const를 땔 수는 없는 노릇이다.
그러면 어떻게 해야 될 까?
결국은 B의 생성자에서 우측값을 받는 이동생성자를 만들고 다시한번 move 를 통해 A의 이동생성자를 선택하게끔 해야된다.
[ Perfect forwarding ]
[ 템플릿 타입 추론 ]
보편적 레퍼런스 (Universal reference)
template <typename T>
void wrapper(T&& u){
g(std::forward<T>(u));
}
https://blog.naver.com/wldlf94/222304202740
https://www.youtube.com/watch?v=6buEm6R980o 참고한 내용.
아래의 코드를 실행하면 a에 아무것도 없는것을 알 수 있다.
std::move(s) 를 통해 a의 "abc" ownership을 b에게 넘겨 줬다.
그런데 함수의 파라미터에 const std::string& s 처럼 const를 붙혀주고 함수 안에서 std::move() 해주게 되면
이동생성자가 아닌 Copy 생성자가 호출이된다.
( 아무래도 string 클래스 내부에서 string&& 은 이동생성자가 호출되는데 const string& s 에 Move() 연산자 하면 string에서이동생성자가 호출되지 않는것 같다. )
const string& s 에 std::move 해주면
const string&& s 가 되고
이때 string 에는
String(const String& ) 복사 생성자
String(String&& ) 이동 생성자
가 정의 되어 있는데 같은 const 성이 있는
복사 생성자를 선택하게 된다. ( 모두의 코드 에도 예시가 나와있다. https://modoocode.com/228)
결론 : const 성이 있는 변수에 std::move를 해주면 이동생성자가 아닌 Copy 생성자가 호출된다.
결국 우리가 원하는 가장 효율적인 방법은
위와 같은 방법이다.
string name 을 파라미터로 받고 내부에서 std::move(name)을 해주면
setName(s) 는 1 번의 copy가 ( 인자로 넘겨줄때 )
setName("nabi") 는 0 copy가 일어난다.
왜 0 Copy가 일어날까?
Copy elision 이 일어나기 때문이다. ( 컴파일러 최적화) - 함수의 인자로 참조가 아닌 임시객체를 넘겨줄때
이 전 글에서 배운 Copy elision은 RVO / NRVO 이고
https://forward-movement.tistory.com/217?category=976217
http://egloos.zum.com/sweeper/v/3204454 - 두번째 내용 2) Passing Temporary as Value 에 나온다.
move() 를 이용하니깐 이동생성자가 호출되서 여기서 복사생성자(copy) 가 일어나지 않는것은 okay 이해 완료.
그럼 std::string name 파라미터에 "nabi" 라는 argument가 대입 될때는 왜 Copy가 일어나지 않는거지? -> copy elision 때문에!
- 컴파일러가 string name에 "nabi" 가 올때 "nabi"의 공간이 따로있고 그 "nabi"를 name의 메모리에 복사하는게 아니라 컴컴파일러가 setName("nabi") 의 인자로 Rvalue가 오는것을 알고 있고, Copy elision 을 통해 "nabi" 라는 임시객체의 값을 name이 그대로 가르키게 해준다. 그리고 함수 안에서 std::move() 를 통해 name은 소유권을 잃고 string의 만들어져있는 이동대입 연산자를 통해 mName이 "nabi"의 공간을 가리키게 되는것이다.
진짜 좋은 설명이 있는 블로그
https://so-what-93.tistory.com/85
갑자기 생각난 주의사항 :
int& RetuRefFunc(int n)
{
int num = 20;
num += n;
return num;
}
int &ref = RetuRefFunc(10); X
함수내 지역변수를 ref 형태로 반환하면 안된다.
컴파일러는 경고메세지만 띄우고 에러메세지는 띄우지 않기 때문에 주의 또 주의해야된다.
이렇게 해제된 메모리를 참조하는 참조자를 댕글링 레퍼런스(Dangling Reference)라고 한다.
함수에서 참조자를 반환할 때는 지역 변수를 반환하여 댕글링 레퍼런스가 발생하지 않았는지 각별히 주의하자.
비쥬얼스튜디오에서 그냥 wanring 만 주기 때문에 진짜로 알기 어렵다.
num 이라는 변수가 메모리에서 소멸됬지만, num이 쓰던 메모리 번지수에는 20이 남아있는것을 VS 돌려보면서 확인했다. 이게 IDE 차원에서 해주는건지 (디버그 모드라?) 모르겠다...
무튼 어찌 됐든 위험한 행동임에는 맞다.
const char* pBuffer = (Hello + World).data();
// 임시객체인 (Hello + World) 은 스택영역에서 정리될 것이다.
// (Hello + World) 에 대해 소멸자가 호출 된다.
// std::string 소멸자는 동적으로 할당된 메모리에 대한 정리 작업을 하도록 구현되어 있다.
// 따라서 pBuffer에 대한 접근은 undefined behavior이 된다.
const std::string& ConstRef = (Hello + World);
const char* pBuffer2 = ConstRef.data();
//const lvalue reference로 참조했다.
//이 경우 계속 참조가 가능해야 하므로 임시객체인 Hello + World 는 죽지 않는다.
'Language & etc > C++' 카테고리의 다른 글
비트 필드 (0) | 2022.07.27 |
---|---|
[컴파일/링킹]에서 본 Static Member Variables, function... (0) | 2022.07.02 |
전방 선언 (Forward Declaration) #22.11.22일 복습 (0) | 2022.06.10 |
LValue, RValue , 이동 생성자 깊게 이해하기 (feat.Copy Elision ) (0) | 2022.05.04 |
타입 변환 연산자 (0) | 2022.04.17 |