다중 정의를 이용한 대입 연산자

CMyData라는 Class의 인스턴스 a와 b가 있을때 a=b 이런것을 수행하면 어떻게 될까????? 당연히 에러난다

하지만 다중정의에서 적절한 값을 넣어준다면 인스턴스간의 = 연산자가 먹힐 수 있다.

 

#include <iostream>
using namespace std;

class CMYData
{

public:
	CMYData(int nParam)
	{
		m_pnData = new int;
		*m_pnData = nParam;
	};

	CMYData(CMYData& rhs)
	{
		m_pnData = new int;
		*m_pnData = *rhs.m_pnData;
	}

	CMYData& operator=(const CMYData& rhs)
	{
		*m_pnData = *rhs.m_pnData;

		return *this;
	}

	~CMYData()
	{
		delete m_pnData;
	}


	int GetData(void) { return *m_pnData; };

private:
	int *m_pnData = nullptr;
};


int main(void)
{
	CMYData a(10);
	CMYData b(20);
	CMYData c(30);

	cout << a.GetData() << endl;
	cout << b.GetData() << endl;
	cout << c.GetData() << endl;

	a = b = c;

	cout << a.GetData() << endl;
	cout << b.GetData() << endl;
	cout << c.GetData() << endl;

	return 0;
};

위의 코드에서 인스턴스 끼리 a=b=c 이러고 있다. 되는 이유는 바로 CMYData& operator=(const CMYData& rhs) 이것 때문이다.

 

operator=은 함수 이름이고 operator+나 operator- 같은 형태로 기본 연산자를 재정의할 수 있다.

여기선 m_pnData의 값을 바꿨다 그래서 a=b=c 코드 이후엔 모든 출력값이 모두 30으로 변경된 것을 알 수 있다.

 

'C++' 카테고리의 다른 글

C++ 객체 생성  (0) 2020.04.02
C++ 임시객체(이동 생성자) 중요!  (0) 2020.02.07
C++ 깊은복사 얕은복사  (0) 2020.02.02
C++ 복사 생성자(참조형 파라미터)  (0) 2020.02.01
C++ this 포인터  (0) 2020.02.01
사전적 의미

깊은 복사는 실제로 두 개의 값(인스턴스가) 생성 되는 것이고, 얕은 복사는 원본은 하난데 포인터가 두개인 것이다.

 

얕은복사로 인한 에러 발생
#include <iostream>
using namespace std;

class CMYData
{
public:
	CMYData(int nParam)
	{
		m_pnData = new int;
		*m_pnData = nParam;
	};

	~CMYData()
	{
		delete m_pnData;
	};


	int GetData(void) { return *m_pnData; };

private:
	int *m_pnData = nullptr;
};


int main(void)
{
	CMYData a(10);
	CMYData b(a);
	cout << a.GetData() << endl;
	cout << b.GetData() << endl;

	return 0;
}

위의 코드를 실행하면 인스턴스 b의 소멸자를 Call 할 때 에러가 발생한다.

왜냐?? 진짜 중요한 개념인데 b의 Default 복사 생성자는 포인터를 제대로 처리하지 못하기 때문이다.

좀 더 자세히 말하면 인스턴스 b도 생성 될 때 새롭게 동적 할당 받아서 거기다가 값을 집어 넣야아하는데 Defalut 복사 생성자는 인스턴스 a의 m_pnData 값 즉 a에서 동적 할당 한 값을 그대로 b의 m_pnData에 집어 넣었기 때문이다. 그러니깐 인스턴스 a가 소멸될때 delete를 했는데 b가 소멸될 때 없는 메모리를 delete 하려 하니 에러가 발생하는것! 

 

깊은복사로 에러 탈출!
	CMYData(CMYData& rhs)
	{
		m_pnData = new int;
		*m_pnData = *rhs.m_pnData;
	}

위와 같은 복사 생성자를 넣어주자. 그럼 인스턴스 b도 생성될때 int 형 만큼의 Heap 메모리를 할당 받아서 그 메모리에 값을 쓴다!

'C++' 카테고리의 다른 글

C++ 임시객체(이동 생성자) 중요!  (0) 2020.02.07
C++ 대입 연산자  (0) 2020.02.02
C++ 복사 생성자(참조형 파라미터)  (0) 2020.02.01
C++ this 포인터  (0) 2020.02.01
C에서 C++ 객체지향의 변환 과정  (0) 2020.01.28
복사 생성자는 객체의 복사본을 생성할 때 호출되는 생성자이다.

CMYData a

CmyData b(a)

 

이처럼 원본 a의 복사본을 만들때 복사 생성자가 불린다.

문법은 클래스이름(const 클래스이름 &rhs) 처럼 사용한다.

 

#include <iostream>
using namespace std;

class CMYData
{
public:
	CMYData() { cout << "기본 생성자" << endl; };
	CMYData(const CMYData &rhs) 
	{
		this->m_nData = rhs.m_nData;
		cout << "복사 생성자" << endl;
	};

	int GetData(void) { return m_nData; };
	void SetData(int nParam) { m_nData = nParam; };

private:
	int m_nData;
};

int main(void)
{
	CMYData a;
	a.SetData(10);

	CMYData b(a);
	cout << b.GetData() << endl;
	return 0;
}

// 출력값
// 기본 생성자
// 복사 생성자
// 10

이런 식으로 CMYData b(a)와 같은 상황일 때 복사 생성자가 불린다. 여기서 rhs는 원본 즉 a 인스턴스의 참조 변수이다.

이것만 봐서는 도대체 복사 생성자가 왜 중요한지 모르겠다. 하지만 매개변수로 사용되는 복사 생성자를 보면 성능에 대해서 아주 중요한 이야기가 나온다. 결론은 가능하면 무조건 참조형으로 받자! 왜냐? 인스턴스가 하나 더 생성 되니깐

참조형으로 받겠다는 말은 주소로 넘기자 이말이다(call by reference)

 

매개변수로 사용되는 복사 생성자
#include <iostream>
using namespace std;

class CMYData
{
public:
	CMYData(int nParam)
		:m_nData(nParam)
	{
		cout << "기본 생성자" << endl; 
	};

	CMYData(const CMYData &rhs) 
		:m_nData(rhs.m_nData)
	{
		cout << "복사 생성자" << endl;
	};

	int GetData(void) { return m_nData; };
	void SetData(int nParam) { m_nData = nParam; };

private:
	int m_nData;
};

void TestFunc(CMYData param)
{
	cout << "TestFunc" << endl;
	param.SetData(20);
}

int main(void)
{
	CMYData a(10);
	TestFunc(a);
	cout << a.GetData() << endl;

	return 0;
}

//출력값
//기본 생성자
//복사 생성자
// TestFunc
// 10

다 집어치우고 main함수에서 TestFunc(a)를 호출하면 복사 생성자가 호출된다 왜냐하면 TestFunc의 매개변수는 CMYData param이기 때문이다. 그러니깐 param은 CMYData의 새로운 인스턴스다. 인스턴스를 하나 더 만들었다.

골때린다. 그리고 그 새로운 인스턴스에 20값을 집어 넣어놓고는 인스턴스가 삭제됐다(스택 프레임 종료). 그러니 a는 여전히 10일 수 밖에.. 아무것도 못하고 성능 낭비 메모리 낭비만 하고 사라지는 거지같은 함수 TestFunc다. 이것을 아주 우아하게 바꾸려면 앞에 & 참조만 붙여주면 된다.

바로 void TestFunc(CMYData &param)이렇게! 이렇게 해야 call by reference로 주소만 넘긴다! 객체를 생성하지도 않고 성능도 아주 좋다.

'C++' 카테고리의 다른 글

C++ 대입 연산자  (0) 2020.02.02
C++ 깊은복사 얕은복사  (0) 2020.02.02
C++ this 포인터  (0) 2020.02.01
C에서 C++ 객체지향의 변환 과정  (0) 2020.01.28
C++ 참조자 형식(reference)  (0) 2020.01.27
this 포인터

this->변수, 파이썬에서는 self.변수 등등 으로 this라는 놈은 OOP에서 상당히 자주 등장하는 개념이고 중요한 개념이다.

C의 포인터와 C++의 객체지향 개념을 이해하면 this라는 놈을 정확하게 알 수 있다. 상당히 귀여운 놈이다.

 

this 포인터는 클래스의 실제 인스턴스에 대한 주소를 가르키는 포인터다.

 

좀 더 풀어서 이야기하면 this는 포인터 변수인데, 실제 인스턴스의 주소를 값으로 가지는 변수이다.

 

#include <iostream>
using namespace std;

typedef struct USERDATA
{
	int nAge;
	char szName[32];
} USERDATA;


class CMYData
{
public:
	CMYData(int nParam) : m_nData(nParam) {};
	void PrintData()
	{
		// 인스턴스 a가 만들어질때는 this와 &a는 같은주소
		// 인스턴스 b가 만들어질때는 this와 &b는 같은주소
		cout << this->m_nData << endl;
		cout << this->CMYData::m_nData << endl;
	}

private:
	int m_nData;
};

int main(void)
{
	CMYData a(5), b(10);
	a.PrintData();
	b.PrintData();
	return 0;
}

- CMYData 클래스 안의 PrintData 메소드는 this를 통해서 m_Data를 접근하고 있다. a.PrintData()를 Call 할 때 this는 &a와 같은 값을 가지고, b.PrintData()를 Call 할 때 this는 &b와 같은 값을 가진다. a.PrintData()는 사실 a.PrintData(&a)와 같다. 파라미터로 &a를 안넘겨도 되는 이유는 C++가 알아서 처리 해주기 때문이다(히든 파라미터라고 부름)

 

this의 실체
class CMYData
{
public:
	CMYData(int nParam) : m_nData(nParam) {};
	void PrintData(CMYData *pData)
	{
		CMYData* myThis = pData;
		// myThis를 통해서 변수에 접근하고있다.
		cout << myThis->m_nData << endl;
		cout << myThis->CMYData::m_nData << endl;
	}

private:
	int m_nData;
};

int main(void)
{
	CMYData a(5), b(10);
	a.PrintData(&a);
	b.PrintData(&b);
	return 0;
}

이번에는 C++가 알아서 처리해주는 히든파라미터 말고 진짜로 주소값을 넘겨봤다. 주소 값이니깐 당연히 포인터를 써주고 그 포인터가 가르키는 대상은 CMYData이다.

'C++' 카테고리의 다른 글

C++ 깊은복사 얕은복사  (0) 2020.02.02
C++ 복사 생성자(참조형 파라미터)  (0) 2020.02.01
C에서 C++ 객체지향의 변환 과정  (0) 2020.01.28
C++ 참조자 형식(reference)  (0) 2020.01.27
C++ new 메모리 동적할당  (0) 2020.01.27
C언어(태초)
#include <iostream>

typedef struct USERDATA
{
	int nAge;
	char szName[32];
} USERDATA;

int main(void)
{
	USERDATA user = { 28, "승우" };
	printf("%d, %s\n", user.nAge, user.szName);
	return 0;
}

간단한 C언어이다. 이 USERDATA라는 놈을 출력 하려면 사용자가 USERDATA 구조체의 구성을 다 알아야 한다. (nAge의 타입은 int니깐 %d로 찍고 등등)

 

만약 USERDATA 구조체를 만든 제작자가 출력해주는 코드를 같이 짜준다면??

#include <iostream>

typedef struct USERDATA
{
	int nAge;
	char szName[32];
} USERDATA;

void PrintData(USERDATA* pUser)
{
	printf("%d, %s\n", pUser->nAge, pUser->szName);
}

int main(void)
{
	USERDATA user = { 28, "승우" };
	PrintData(&user);
	return 0;
}

사용자는 그저 PrintData를 호출하기만 하면 된다. USERDATA의 멤버의 구성을 일일히 다 알 필요가 없다는 뜻!

이런 PrintData를 인터페이스 함수라고 부른다.(내부는 모르겠고 인터페이스를 통해서 알아서 좀 해줘~!)

 

근데 좀 허전하다 USERDATA 구조체와 PrintData가 무슨 관계가 있는지 어떻게 아냐? 좀 묶을 필요가 있다. 사용자가 헷갈리지 않도록.

 

구조체안에 함수도 한번 넣어봐라.

#include <iostream>

typedef struct USERDATA
{
	int nAge;
	char szName[32];
	void(*MyPrint)(struct USERDATA*);
} USERDATA;

void PrintData(USERDATA* pUser)
{
	printf("%d, %s\n", pUser->nAge, pUser->szName);
}

int main(void)
{
	USERDATA user = { 28, "seungwoo", PrintData};
	user.MyPrint(&user);
	return 0;
}

구조체 USERDATA의 주소값을 파라미터로 가지는 MyPrint 함수를 구조체 안에 넣었다. 그리고 PrintData로 초기화했다.

진짜 구조체안에 함수를 넣으려고 애썼다. 근데 한가지 아쉬운점은 자기자신의 주소를 한번 더 파라미터로 넘긴다는점.

야 니꺼 니가 접근하는데 도대체 니 주소를 왜...?? 안되겠다 Class 만들자.

 

C++ Class  탄생
class USERDATA
{
public:
	int nAge;
	char szName[32];

	void MyPrint(void)
	{
		std::cout << this->nAge << " " << this->szName << std::endl;
	}
};


int main(void)
{
	USERDATA user = { 28, "seungwoo"};
	user.MyPrint();
	return 0;
}

이런식으로 만들면 정말 이쁘다 이뻐.. c++에서 MyPrint 메소드를 호출할 때 사실은 &user가 파라미터로 넘어간다. 이것을 히든 파라미터라고 부른다.

'C++' 카테고리의 다른 글

C++ 깊은복사 얕은복사  (0) 2020.02.02
C++ 복사 생성자(참조형 파라미터)  (0) 2020.02.01
C++ this 포인터  (0) 2020.02.01
C++ 참조자 형식(reference)  (0) 2020.01.27
C++ new 메모리 동적할당  (0) 2020.01.27
참조자 형식

참조자 형식은 겉으로 보기엔 포인터와 다르지만 본질은 C언어의 포인터와 비슷하다. 

사용법은 "형식 &이름 = 원본"과 같이 반드시 선언과 동시에 초기화를 해줘야한다.

 

아래와 같이 nData의 참조자인 ref 변수를 만들었다. 그리고 메모리에서 &nData와 &ref를 둘다 까봤다.

결과는 008FFE2C로 둘다 같은 메모리 주소를 가지는 것 처럼 나온다. 그러니 ref변수에서 값을 20으로 바꾸면 원본인 nData의 값도 바뀌게 된다. 마치 포인터를 쓰는것과 같다.

	int nData = 10;
	int& ref = nData;

	ref = 20;
	cout << nData << endl;

	int* pData = &nData;
	*pData = 30;
	cout << nData << endl;

ref와 nData는 같은 주소

 

 

참조에 의한 호출, 주소에 의한 호출 예제
#include <iostream>
#include <string.h>
using namespace std;

class CIntArray
{
public:
	CIntArray(int nSize)
	{
		m_pnData = new int[nSize];
		memset(m_pnData, 0, sizeof(int) * nSize);
	}
	
	~CIntArray()
	{
		delete m_pnData;
	}
	
	int& operator[](int nIndex)
	{
		cout << "operator[]" << endl;
		return m_pnData[nIndex];
	}

private:
	int* m_pnData;
	int m_nSize;
};

int main()
{
	CIntArray arr(5);
	for (int i = 0; i < 5; ++i)
	{
		arr[i] = i * 10;
		cout << arr[i] << endl;
	}

	return 0;
}

 

CIntArray는 nSize 크기 만큼 int array를 만드는 클래스다(동적할당)

주목해야 할 점은 int& operator[](int nIndex)다.

여기서 int&는 참조형을 반환 하는데 무엇에 대한 참조형일까?

m_pnData 포인터의 요소 즉 int에 대한 참조형이다. 왜냐하면 m_pnData가 포인터인데 연속된 int형을 가르키는 포인터니깐!

 

여기서 참조를 빼면 "식이 수정할 수 있는 lvalue여야 합니다" 라는 에러가 난다.  이게 무슨 뜻일까?

nIndex가 0이라고 가정하면 m_pnData[0]에 해당하는 주소값(참조)을 넘겨야 그 주소값에 i * 10 값을 넣는데 &(참조)를 빼면 m_pnData[0]의 값을 return 하니 위와 같은 에러가 뜬다. 

참조는 곧 포인터라 했다 참조를 포인터로 고치는건 아주 간단한다.

 

참조에서 포인터로 변경
#include <iostream>
#include <string.h>
using namespace std;

class CIntArray
{
public:
	CIntArray(int nSize)
	{
		m_pnData = new int[nSize];
		memset(m_pnData, 0, sizeof(int) * nSize);
	}
	
	~CIntArray()
	{
		delete m_pnData;
	}
	
	int operator[](int nIndex) const
	{
		cout << "const" << endl;
		return m_pnData[nIndex];
	}
	
	int* operator[](int nIndex)
	{
		cout << "operator[]" << endl;
		return &m_pnData[nIndex];
	}

private:
	int* m_pnData;
	int m_nSize;
};

int main()
{
	CIntArray arr(5);
	for (int i = 0; i < 5; ++i)
	{
		*(arr[i]) = i * 10;
		cout << *(arr[i]) << endl;
	}

	return 0;
}

1. int& operator[]를 int*로 바꿨다.

2. return 할때 값이 아니라 주소로 바꿨다(&m_pnData[nIndex])

3. 대입할 때 *(arr[i])로 변경했다.

 

결론 : 참조는 포인터다.

'C++' 카테고리의 다른 글

C++ 깊은복사 얕은복사  (0) 2020.02.02
C++ 복사 생성자(참조형 파라미터)  (0) 2020.02.01
C++ this 포인터  (0) 2020.02.01
C에서 C++ 객체지향의 변환 과정  (0) 2020.01.28
C++ new 메모리 동적할당  (0) 2020.01.27
메모리 동적 할당

C언어에 malloc이 있다면 C++에는 new 연산자가 있다!

사용 방법은 정말 심플하다 

// 일반
형식 *변수이름 = new 형식;
delete 변수이름;

// 배열 형태
형식 *변수이름 = new 형식[요소개수];
delete[] 변수이름;
    // 일반
    
	int* pData = new int;
    int* pNewData = new int(10);

    *pData = 5;
    std::cout << *pData << std::endl;
    std::cout << *pNewData << std::endl;

    std::cout << "Hello World!\n";


    // 배열
    int* arr = new int[5];

    for (int i=0; i < 5; ++i)
    {
	    arr[i] = (i + 1) * 10;
    }

    for (int i=0; i < 5; ++i)
    {
	    std::cout << arr[i] << std::endl;
    }

    delete[] arr;

위와 같이 사용한다.

 

new 연산자와 malloc을 비교했을때 가장 큰 차이점은 메모리 크기를 정하지 않아도 알아서 해준다는 점이다.

그리고 하나 더 new 연산자는 객체의 생성자를 호출하고, delete 연산자는 객체의 소멸자를 호출한다는 점이다

 

'C++' 카테고리의 다른 글

C++ 깊은복사 얕은복사  (0) 2020.02.02
C++ 복사 생성자(참조형 파라미터)  (0) 2020.02.01
C++ this 포인터  (0) 2020.02.01
C에서 C++ 객체지향의 변환 과정  (0) 2020.01.28
C++ 참조자 형식(reference)  (0) 2020.01.27

+ Recent posts