LANG

복사생성자 / extern 키워드

혀니리리 2024. 2. 6. 20:40
728x90

*복사생성자 (기본으로 생성되는 것 중 하나)
의미 : 클래스를 복사하여 생성하는 생성자
기본생성자 - 단 한개의 생성자라도 명시적으로 구현되어 있으면 생성 안 됨
복사생성자 - 단 한개의 생성자라도 명시적으로 구현되어 있으면 상성 안 됨
(기본생성자와 마찬가지)
 
<속도 비교>
복사생성자 > 기본생성자
복사생성자 : 기본생성자를 생성 + 오버로딩까지 해야 하니 더 느림
 
<그럼에도 복사생성자 쓰는 이유>
기존: 하나하나 생성자로 만들면 몬스터 10마리 만들 때 멤버변수가 20개면 총 200번의 초기화를 해야한다.
현재: 복사생성자를 사용하면 메모리 전체를 복사하기 때문에 1:1 초기화를 할 필요 없이 한번에 메모리를 전부 복사해준다. (다수의 멤버 복사 시 이득을 보게 된다.)
 
<복사생성자 발생 시점 3가지>
1. 먼저 만든 객체를 매개 변수로 전달하여 객체를 생성하는 경우

#include "stdafx.h"

class CObj
{
public:
	CObj(int _iA)
	{
		m_iA = _iA;
		cout << "매개변수가 있는 생성자 호출" << endl;
	}
private:
	int m_iA;
	int m_iB = 10; int m_iC = 20;
};

int main()
{
	CObj Origin(100);
	Origin.Render();
	CObj Copy(Origin); //1.먼저 만든 객체를 매개변수로 전달하여 객체를 생성하는 경우
	Copy.Render();
	return 0;
}

출력 결과: 

더보기

매개변수가 있는 생성자 호출

100 10 20

100 10 20

=> 복사생성자를 명시적으로 만들지 않아도 default 복사생성자가 만들어져
알아서 기본생성자에서 부여한 매개변수의 값 그대로 잘 출력한다.
 
<만약, 복사생성자를 명시적으로 생성한다면?>

	CObj(const CObj& rhs)	//복사생성자의 명시적 호출은 이런식으로 씁니다.
	{
		m_iA = rhs.m_iA;	//원본 객체를 사본 객체에 복사한다는 의미
							//명시적 호출해줄 때에는 이렇게 일일히 할당해줘야 제대로된 결과가 나옴
		cout << "복사 생성자 호출" << endl;
	}
더보기

<복사생성자 매개변수의 의미>

& : 원본참조

const: 읽기 전용

rhs : right hand side(오른손 ) - 복사생성자 매개변수(원본객체)로 많이 씀

총합 : "원본을 읽기 전용으로 참조하겠다."

 
출력 결과:

	매개변수가 있는 생성자 호출
	100 10 20
	복사 생성자 호출 //==> 복사생성자는 default기본생성자가 아닌 default복사생성자를 부른다.
	100 10 20

 
2. 함수의 매개 변수가 객체 타입이어서 복사 생성을 수행

#include "stdafx.h"

class CObj
{
public:
	CObj(int _iA)
	{
		m_iA = _iA;
		cout << "매개변수가 있는 생성자 호출" << endl;
	}
public:
	void Output(CObj Temp) { //2.함수의 매개변수가 객체 타입이어서 복사 생성을 수행.
		Temp.Render();
	}
private:
	int m_iA;
};

int main()
{
	CObj Test(200);
	Origin.Output(Test);//깊은복사가 이루어짐.(이 때만 200으로 나오고 Origin은 100..)
    
	return 0;
}

깊은복사가 이루어져 call by value로 값이 복사되어 전달된다.
 
3. 함수의 반환 타입이 객체 타입이어서 복사 생성을 수행

#include "stdafx.h"

class CObj
{
public:
	CObj(int _iA)
	{
		m_iA = _iA;
		cout << "매개변수가 있는 생성자 호출" << endl;
	}
	CObj(const CObj& rhs)
	{
		m_iA = rhs.m_iA;
        cout << "복사 생성자 호출" << endl;
	}
public:
	void Render() { cout << m_iA << " " << m_iB << " " << m_iC << endl; }
	CObj Get_Obj()
	{
		return *this;
	}
private:
	int m_iA;
	int m_iB = 10; int m_iC = 20;
};

int main()
{
	CObj Origin(100);
	Origin.Get_Obj().Render();

	return 0;
}

출력결과:

더보기

복사 생성자 호출
100 10 20

이 세가지 경우 복사생성자를 호출한다.
2,3은 볼 일이 희박(대부분 우리가 만드는 함수들은 포인터를 활용하지 객체를 활용하지 않음)
 
복사생성자는 구조체에서도 쓰임..
(ex) 텍알에서 복사생성자 쓰인 예시 Get_Info() => 3)
(ex2) INFO Temp(m_tInfo); => 2)
(ex3) 프로토타입 디자인패턴
CObj*    Create()  {  return new CObj(*this);  } => 3)

<240206.cpp>

#include "stdafx.h"
#include <stdio.h>
#include <iostream>
#include "Player.h"

using namespace std;

int g_iNumber = 10;

class CName
{
public:
	CName(const char* pName)
	{
		int iLength = strlen(pName) + 1;

		m_pName = new char[iLength];

		strcpy_s(m_pName, iLength, pName);
	}
	~CName() { delete[] m_pName; }

public:
	void PrintN() { printf("%s\n", m_pName); }

private:
	char* m_pName;
};

void Draw(CName Name) { Name.PrintN(); }

int main()
{
	CName Name("neo - peple");
	Draw(Name);

	CPlayer player;
	player.Insert();
	return 0;
}

컴파일 결과: 
neo-peple이 출력된 후 컴파일 에러
(디폴트복사생성자로 복사 자체는 문제 없음
프로그램이 돌고 나서 터짐 => 런타임 오류
소멸자 호출 시점에 문제 발생)
 
<문제 원인>
소멸자 호출 시점에 문제 발생
둘다 힙공간에 있는것을 씀(동적할당된 매개변수.) => <얕은 복사에 의한 문제..>
주소가 공유되어 소멸이 두번 일어남..
 
<해결 방법>
서로 힙공간을 따로 만들고 따로 해제시키기.

	CName(const CName& rhs)
	{
		//m_pName = rhs.m_pName; //default복사생성자는 이런식으로 얕은복사를 해줘 메모리 해제 때 문제가 발생한다.
		int iLength = strlen(rhs.m_pName) + 1;

		m_pName = new char[iLength]; //이렇게 힙메모리에 별도의 저장공간을 만들고 거기에 내용을 복사해줘야 소멸자 문제가 발생하지 않음

		strcpy_s(m_pName, iLength, rhs.m_pName);
	}

 
//동적할당 따로따로 할당하기 때문에 문제가 일어나지 않음 => 깊은복사

<또다른 해결책들>
1.생성자 안에서 동적할당 안하면 해결될 문제긴 함
(동적할당을 생성자 아닌 시점에서 하면 얕은복사 문제가 일어나지 않음)

2.원본참조로 void Draw(CName& Name)해주면 끝남.


<extern>
쓰임) 전역변수 int g_iNumber=  10;를 클래스 멤버함수 안에서 전역변수를 출력하고싶을때
전역변수는 한 파일안에서만 유효함
 
<이를 가능하게 하려면>
똑같은 타입의 변수 이름을 player.cpp에서
extern int g_iNumer;
외부파일의 전역변수 이름을 사용할 수 있게 해주는 키워드.(존재를 알려주는 용도)
 
<한계>
extern -> 알려주기만 하지 용량을 할당하거나 하지는 않음
extern int g_iNumber ; => 메모리 용량이 없음.

특징) 원본이 없는 경우라면 얘 자체가 원본이 됨
원본 있는 경우라면 초기화식은 수행될 수 없음

보통 쓰는 방법) 헤더파일에다가 extern 선언해놓고 씀

함수도 extern 가능하지만 객체지향적 성격에는 맞지 않음.
(ex) API 창 구성하는 handle 을 extern으로 전역으로 선언함)

+ ) malloc으로 동적할당할 때 생길 수 있는 문제? => 생성자가 호출되지 않음.

728x90