복사 및 이동 생성자.

보물창고·2021년 7월 27일
0
  • 코드없는 프로그래밍 강의를 보고 공부 및 정리한 내용입니다.

내용 추가 240625

char * 을 멤버로 가지고 있을 때의 복사 ,이동 생성자에 대해서

#include <iostream>
#include <vector>
#include <string>
using namespace std;

class Cat
{
private:
	char* mName = nullptr;
	int mAge;
public:

	Cat() = default;

	Cat(const char* name, int age) :  mAge{ age }
	{
		int ssize = sizeof(name);
		mName = new char[sizeof(name)  +1];
		memcpy(mName, name, ssize);
		cout << mName << "constructor" << endl;
	}

	~Cat()
	{
		static int n = 0;
		++n;
		if (mName != nullptr)
		{
			cout << mName << "destructor" << endl;
			delete mName;
		}
		else
		{
			cout << "이미 해지됨 ! " << endl;
		}

	}

	//복사 생성자.
	Cat(const Cat& other) : mAge{ other.mAge }
	{
		int ssize = sizeof(other.mName);
		mName = new char[sizeof(other.mName) + 1];
		memcpy(mName, other.mName, ssize);
		cout << mName << "Copy 생성자 " << endl;
	}

	//Cat(const Cat& other) = default;
	//Cat(Cat&& other) = default;

	//이동 생성자.
	Cat(Cat&& other) noexcept 
		: mName{ move(other.mName) }, mAge{ other.mAge } 
	{
		other.mName = 0;
		cout << mName << "이동 생성자 " << endl;
	}


	void print()
	{
		if (mName != nullptr)
			cout << mName << " " << mAge << endl;
		else
			cout << "이미 nullptr" << endl;
	}
};


int main()
{
	Cat kitty{ "키티", 1 };

	Cat kitty2{ kitty };
	Cat kitty3 = move(kitty);
	kitty.print();
	kitty2.print();
	kitty3.print();

	return 0;
}
  • 결과
    : 이 때는 복사 생성자와 이동 생성자를 유저가 잘 만들었기 때문에
    메모리 릭 발생하지 않음.

  • 프로젝트 코드 중에 이동생성자랑 복사 생성자에 default 처리된 것이 있어서 궁금했다.
    : 힙 멤버가 있는데 이 때도 가능할지??
    -> 당연하게도 힙메모리 접근 오류 발생한다.


복사, 이동생성자 등을 정의하는 이유

: 유저가 직접 동적할당하는 new 의 경우에 얕은 복사를 방지하기 위해서이다.
vector, string, 스마트 포인트는 무관한다.

클래스에서 컴파일러가 알아서 만들어주는 함수는

1) 생성자
2) 소멸자
3) copy / move 생성자
4) copy / move 대입 연산자

  • 유저가 클래스 내의 멤버 변수로 포인터를 사용한다면 소멸자, copy /move 생성자 및 할당자를 관리해야 한다.
    -> 포인터가 클래스 내에 없다면 구현할 필요는 없다.

초기화 할때는 습관적으로 brace initialization - {} 사용하자.

https://blog.naver.com/kimwontae466/222388846175
https://modoocode.com/286

변환 생성자.

  • 변환 생성자를 만들어 주었기 때문에 컴파일러가 자동적으로 생성자를 호출하지 않게 되는 에러가 발생하고, 2가지 방법으로 문제를 해결해야 한다.

1) 변환 생성자의 매개변수를 초기화하자.

2) 유저가 디폴트 생성자를 만든다.
3) 디폴트를 명시시킴으로, 컴파일러 생성자를 호출하도록 한다.

생성자와 소멸자.

복사 생성자

: 기존 오브젝트를 복사해 새로운 오브젝트를 만들때 사용하는 생성자.

-> kitty3 = kitty는 equal로 인해 대입이 사용되었다 라고 생각하겠지만,
실제로는 새로운 오브젝트가 생성이 되는 과정이어서 복사 생성자가 호출된다.
=> 추가적으로 이러한 모호성, 즉 복사생성인지, 복사 대입인지 애매하므로,
{}를 이용해서 오브젝트를 생성하는 것을 권장한다.

이동 생성자.

: 기존 객체의 주소 및 value값을 새로운 오브젝트에 소유권을 이전하게 하는 생성자로, 기존 객체의 값이 이전된다.

#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;
	}


	void print()
	{
		cout << mName  << " " << mAge << endl;
	}
};


int main()
{
	Cat kitty{"키티", 1};

	Cat kitty2{ kitty };
	Cat kitty3 =  kitty ;

	return 0;
}

-> 여기서 우리는 포인터가 아닌 string을 사용중이므로 move를 통해 소유권을 이전시킬 수 잇다.
=> 이동생성자를 호출하기 위해서는 호출 부에서 rValue를 넣어줘야 하므로
move를 이용해 호출하도록 하자.

멤버 변수가 존재할 경우에 복사와 이동생성자에서의 처리

  • 만약에 mPtr이라는 포인터가 있을 경우에는

  • 복사 생성자에서는 memcpy를 통해 메모리를 복사하자.
    : 한글의 경우, 2바이트 이기 때문에 대입을 통해서는 불가하다.

  • 이동생성자에서는 유저가 대입해서 얕은 복사로 옮기고, 포인터를 nullptr로
    만듦으로써 소유권을 이전시키자.

#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;
	}


	void print()
	{
		cout << mName  << " " << mAge << endl;
	}
};


int main()
{
	Cat kitty{"키티", 1};

	Cat kitty2{ kitty };
	Cat kitty3{ move(kitty) };

	return 0;
}

rule of 3 / 5 등이 있다. - 공부하자.

profile
🔥🔥🔥

0개의 댓글