copy assignment, move assignment (대입)
- 코드 없는 프로그래밍 강좌를 공부하고 정리한 내용입니다.
- "객체 = 객체" 일 경우에 복사 및 이동 대입 생성자가 호출된다.
copy assignment
-
갱신되는 객체가 원본? 기준? 이 되는 객체를 대입할 경우 copy 형식으로
진행이 되는 함수.
-
복사 대입생성자가 호출되기 위해서는 초기화된 2개의 객체가 필요하다.
소스코드
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Cat
{
private :
string mName;
int mAge;
public :
Cat() = default;
Cat(string name , int age ) : mName{ move(name) }, mAge{age}
{
cout << mName << "constructor" << endl;
}
~Cat()
{
cout << mName << "destructor" << endl;
}
//복사 생성자.
Cat(const Cat& other) : mName{ other.mName }, mAge{ other.mAge }
{
cout << mName << "Copy 생성자 " << endl;
}
//이동 생성자.
Cat(Cat && other) :mName{ move(other.mName) }, mAge{ other.mAge }
{
cout << mName << "이동 생성자 " << endl;
}
Cat& operator=(const Cat & other)
{
mName = other.mName;
mAge = other.mAge;
cout << mName << " copy assignment " << endl;
return *this;
}
void print()
{
cout << mName << " " << mAge << endl;
}
};
int main()
{
Cat kitty{"키티", 1};
Cat navi{ "나비", 2 };
kitty = navi;
kitty.print();
return 0;
}
-
선언과 동시에 대입하게 되면 어떻게 될까?
-> 앞의 강의에서 공부한 것처럼, 컴파일러가 센스있게 선언과 동시에 초기화하면 복사생성자를 호출시킨다.
-
복사 생성자 일 때의 주소 비교
-> 깊은 복사가 진행되었다.
-
복사 대입연산자 일 때의 주소 비교
-> 깊은 복사가 진행되었다.
복사 생성자 vs 복사 대입생성자.
-
객체의 value를 복사하는 의미는 동일하다.
-
복사생성자는 갱신되는 객체의 선언과 동시에 원본을 받아 초기화할 때 호출된다.
-
복사 대입 생성자는 2개의 객체가 모두 초기화가 이루어진 후에 대입을 할
경우에 호출된다.
이동 대입 생성자란?
갱신되는 객체가 원본? 기준? 이 되는 객체를 대입할 경우 copy 형식으로
진행이 되는 함수.
복사 대입생성자가 호출되기 위해서는 초기화된 2개의 객체가 필요하다.
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Cat
{
private :
string mName;
int mAge;
public :
Cat() = default;
Cat(string name , int age ) : mName{ move(name) }, mAge{age}
{
cout << mName << "constructor" << endl;
}
~Cat()
{
cout << mName << "destructor" << endl;
}
//복사 생성자.
Cat(const Cat& other) : mName{ other.mName }, mAge{ other.mAge }
{
cout << mName << "Copy 생성자 " << endl;
}
//이동 생성자.
Cat(Cat && other) :mName{ move(other.mName) }, mAge{ other.mAge }
{
cout << mName << "이동 생성자 " << endl;
}
Cat& operator=(const Cat & other)
{
mName = other.mName;
mAge = other.mAge;
cout << mName << " copy assignment " << endl;
return *this;
}
void print()
{
cout << mName << " " << mAge << endl;
}
};
int main()
{
Cat kitty{"키티", 1};
Cat navi{ "나비", 2 };
kitty = navi;
kitty.print();
return 0;
}
선언과 동시에 대입하게 되면 어떻게 될까?
-> 앞의 강의에서 공부한 것처럼, 컴파일러가 센스있게 선언과 동시에 초기화하면 복사생성자를 호출시킨다.
복사 생성자 일 때의 주소 비교
-> 깊은 복사가 진행되었다.
복사 대입연산자 일 때의 주소 비교
-> 깊은 복사가 진행되었다.
객체의 value를 복사하는 의미는 동일하다.
복사생성자는 갱신되는 객체의 선언과 동시에 원본을 받아 초기화할 때 호출된다.
복사 대입 생성자는 2개의 객체가 모두 초기화가 이루어진 후에 대입을 할
경우에 호출된다.
: 갱신 받을 객체가 원본, 기준이 되는 객체를 RValue 형식으로 받으면
move 형식으로 진행이 되는 함수
-> navi의 mName이 kitty에게 빼앗껴서 navi의 소멸자(생성을 가장 늦게 했으니 소멸자는 가장 먼저 호출됨.)에서 mName값이 나오지 않는 것을 확인할 수 있따.
-> 원본이 다른 공간으로 이동된다.
한번더 생각해야 할점.
: 자기 자신을 대입하거나, move 하면 어떻게 될까?
-> 일반적인 데이터 타입은 이상이 없다.
- 하지만, 멤버 변수로 포인터가 있다면, 얕은 복사 문제가 발생할 것이다.!
-> 자기 자신이 대입되는 것을 방지하기 위해 아래와 같이 예외처리를 하자.
noexcept 사용하기
: 소멸자, 이동생성자, 이동 대입 생성자는 noexcept를 사용해서 예외처리 되는 것을 방지하자.
-> 3개의 함수는 새로운 리소스? 를 요청하지 않으므로 exception이 발생할 수 없다. noexcept를 붙이면 컴파일러가 예외처리를 하지 않으므로 3개의 함수가 반드시 호출된다.
- 실질적으로 클래스 내에 포인터 변수를 관리하지 않는다면, 클래스 내에서
이것들을 직접 만들 필요는 없다. 컴파일러가 알아서 관리한다.
완료된 소스 코드
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Cat
{
private :
string mName;
int mAge;
public :
Cat() = default;
Cat(string name , int age ) : mName{ move(name) }, mAge{age}
{
cout << mName << "constructor" << endl;
}
~Cat() noexcept
{
cout << mName << "destructor" << endl;
}
//복사 생성자.
Cat(const Cat& other) : mName{ other.mName }, mAge{ other.mAge }
{
cout << mName << "Copy 생성자 " << endl;
}
//이동 생성자.
Cat(Cat && other) noexcept :mName{ move(other.mName) }, mAge{ other.mAge }
{
cout << mName << "이동 생성자 " << endl;
}
Cat& operator=(const Cat & other)
{
if (&other == this)
{
return *this;
}
mName = other.mName;
mAge = other.mAge;
cout << mName << " copy assignment " << endl;
return *this;
}
//이동 대입 생성자.
Cat& operator=(Cat && other) noexcept
{
if (&other == this)
{
return *this;
}
mName = move(other.mName);
mAge = other.mAge;
cout << mName << " move assignment " << endl;
return *this;
}
void print()
{
cout << mName << " " << mAge << endl;
}
};
int main()
{
Cat kitty{"키티", 1};
Cat navi{ "나비", 2 };
kitty = kitty;
kitty = move(kitty);
return 0;
}
- 복사 생성자, 이동 생성자, 복사 대입, 이동 대임 모두 지우고
main부에서 복사 및 이동 하더라도 문제되지 않는다.
-> 생산성이 높은 c++프로그램을 작성하기 위해서는 클래스 내에서 포인트 사용한 리소스 관리는 최대한 피하자.
복사를 피하기 위해서 이렇게 작성함.
-> 이동생성자도 마찬가지
-> 복사 생성자 호출되지 않는 것을 출력창을 통해서 확인할 수 있다.
오브젝트 생성을 아예 못하게 할 수 있따.
- 언제 사용할까?
1) static 메소드나 변수를 가지고 있는 클래스의 경우
2) 싱글톤 사용할때
복사, 이동, 대입 생성자 안할때
- 각 생성자를 만들고 뒤에다가 delete를 표기하자.
- 간혹 코드 리뷰 시에 private에 복사, 이동, 대입 생성자를 선언하는 경우가 있는데 c++11 이전의 코드이고, public에서 delete를 한것과 동일한 기능이다.
Author And Source
이 문제에 관하여(copy assignment, move assignment (대입)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@kwt0124/copy-assignment-move-assignment-대입저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)