copy assignment, move assignment (대입)

보물창고·2021년 7월 27일
0
  • 코드 없는 프로그래밍 강좌를 공부하고 정리한 내용입니다.
  • "객체 = 객체" 일 경우에 복사 및 이동 대입 생성자가 호출된다.

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개의 객체가 모두 초기화가 이루어진 후에 대입을 할
    경우에 호출된다.

이동 대입 생성자란?

: 갱신 받을 객체가 원본, 기준이 되는 객체를 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를 한것과 동일한 기능이다.
profile
🔥🔥🔥

0개의 댓글