read

열세번째 열혈강의


coffee

이제 복사생성자까지 우리는 알게 되었다. 이번에는 지난번부터 단계별로 진행하던 OOP 단계별 프로젝트 3단계부터 진행하자.


OOP 단계별 프로젝트 03단계


이번단계에서는 Account 클래스에 깊은 복사를 진행하는 복사 생성자를 정의해보자.

이번 프로젝트에서는 이게 전부이기 때문에 크게 어렵지 않을 것이다.

깊은 복사를 원칙으로 하면, 클래스의 생성자만 봐도 복사 생성자의 필요성을 판단할 수 있다. 실제로 복사 생성자의 호출여부는 크게 중요치 않다. 클래스는 그 자체로 완성품이기 때문에, 당장 필요한 것만으로 채우진 않는다.

꼭 본인이 해보고 다음 코드를 보도록 하자.

class Account {
private: 
	int accID;
	int balance;
	char* cusName;
public:
	Account(int ID, int money, char* name) : accID(ID), balance(money) {
		cusName = new char[strlen(name) + 1];
		strcpy(cusName, name);
	}

	Account(const Account & ref) : accID(ref.accID), balance(ref.balance) {
		cusName = new char[strlen(name) + 1];
		strcpy(cusName, ref.cusName);
	}

	int GetAccID() { return accID; }
	
	void Deposit(int money) {
		balance += money;
	}
	
	int Withdraw(int money) {
		if (balance < money) {
			return 0;
		}

		balance -= money;
		return money;
	}

	void ShowAccInfo() {
		cout << "계좌ID : " << accID << endl;
		cout << "이름 : " << cusName << endl;
		cout << "잔액 : " << balance << endl;
	}

	~Account(){
		delete[] cusName;
	}
		 
};

friend, static, const


이제 chapter6에 들어왔다.

아마 점차 상속의 개념이 들어갈 것 같다.

먼저 지금은 못다한 const에 대해 얘기해 보자.

C언어랑 달리 C++에서는 const에 대해 알아야 할 것이 많다.

const 객체와 const 객체의 특성부터 알아보도록 하자.

const int num = 10;

다음과 같이 변수를 상수화 하듯,

const SoSimple sim(20)

처럼 객체도 상수화가 가능하다.

그리고 이렇게 객체에 const 선언을 하면, 이 객체를 대상으로는 const 멤버함수만 호출이 가능하다. 왜냐하면 이 객체에 const 선언은

이 객체의 데이터 변경을 허용하지 않는다 라는 의미이기 때문이다.

때문에 const 멤버함수의 호출만 허용한다. 물론 const로 선언되지 않은 함수 중에도 데이터를 변경하지 않을 수 있지만 아예 그럴 가능성이 있으면 호출을 허용하지 않게 된다.

#include <iostream>

using namespace std;

class SoSimple {
private:
	int num;
public:
	SoSimple(int n) : num(n){}
	SoSimple& AddNum(int n) {
		num += n;
		return *this;
	}

	void ShowData() const {
		cout << "num : " << num << endl;
	}
};

int main(void) {
	const SoSimple obj(7);
	//obj.AddNum(20);
	obj.ShowData();
	return 0;
}

이런 예제를 통해서 알수 있듯, 멤버변수에 저장된 값을 수정하지 않는 함수는 가급적 const선언을 해서 const 객체에서도 호출이 가능하도록 할 필요가 있다.


const 함수 오버로딩


swing

함수의 오버로딩이 성립하려면 파라미터의 수나 자료형이 달라야 한다. 하지만 다음처럼 const의 선언유무도 함수 오버로딩의 조건이 된다.

void SimpleFunc(){...}

void simpleFunc() const{...}

이런 const 함수 오버로딩이 어떻게 활용되는지는 다음에 보도록 하고, 일단 이를 문법적으로 기억해 두도록 하자.

#include <iostream>

using namespace std;

class SoSimple {
private:
	int num;
public:
	SoSimple(int n) : num(n){}
	
	SoSimple& AddNum(int n) {
		num += n;
		return *this;
	}
	
	void SimpleFunc() {
		cout << "SimpleFunc : " << num << endl;
	}

	void SimpleFunc() const {
		cout << "const SimpleFunc : " << num << endl;
	}
};

void YourFunc(const SoSimple &obj) {
	obj.SimpleFunc();
}

int main(void) {
	SoSimple obj1(2);
	const SoSimple obj2(7);

	obj1.SimpleFunc();
	obj2.SimpleFunc();

	YourFunc(obj1);
	YourFunc(obj2);
	return 0;
}

위는 간단한 예시인데, const 선언을 했을 때는 const 함수가, 아닐때는 다른 함수가 호출 된다.

YourFunc에도 파라미터에 const가 있으므로 const 선언했을 때의 함수가 호출됨을 볼 수 있다.


friend


friend는 친구라는 뜻이다. 따라서 friend 선언은 “넌 오늘부터 내 친구” 라는 대화에 비유할 수 있다.

A라는 클래스와 B라는 클래스가 있다고 하자. 이 상황에서 A 클래스는 B 클래스에게 다음과 같이 말한다.

“B야 오늘부터 넌 내 친구다”

그리고는 그 다음부터 A 클래스는 B 클래스에게 자신의 속마음을 다 보여준다. 그런데 B는 아직 A를 친구라고 생각하지 않는다. 따라서 아직 속마음을 보여주지 않았다.

그러다 B도 역시 A에게

“A야 오늘부터 넌 내 친구야”

라고 말하고 속마음을 보인다. 이를 정리해보자.

A 클래스가 B 클래스를 대상으로 friend 선언을 하면, B 클래스는 A 클래스의 private 멤버에 직접 접근이 가능하다.

단, A 클래스도 B 클래스의 private 멤버에 직접 접근이 가능하려면, B 클래스가 A 클래스를 대상으로 friend 선언을 해줘야 한다.

이렇듯 friend 선언은 private 멤버의 접근을 허용하는 선언이다. 그럼 다음을 보자.

class Boy{
private:
	int height;
    friend class Girl;
public:
	Boy(int len) : height(len){}
    ...
}

위의 Boy 클래스는 Girl 클래스를 friend 선언하였다. 따라서 Girl 클래스 내에서는 Boy 클래스의 모든 private 멤버에 직접 접근이 가능하다. 참고로 friend 선언은 클래스 내에 어디든 위치할 수 있다. private든 public이든 상관없다. 그럼 Girl을 보도록 하자.

class Girl{
private:
	char phNum[20];
public:
	Girl(char* num){
    	strcpy(phNum, num);
    }
    void ShowYourFriendInfo(Boy &frn){
    	cout << "His height : " << frn.height << endl;
    }
}

위 클래스의 ShowYourFriendInfo 함수 내에서 Boy 클래스의 private 멤버인 height에 직접 접근을 하고 있다. Girl 클래스는 Boy 클래스의 friend이므로, Girl 클래스 내에 선언된 모든 멤버함수는 이렇듯 Boy 클래스의 private 멤버에 접근이 가능하다. 그럼 이 두 클래스를 기반으로 예제를 하나 보도록 하자.

#include<iostream>
#include<cstring>
using namespace std;

class Girl;

class Boy {
private:
	int height;
	friend class Girl;
public:
	Boy(int len) : height(len) {}
	void ShowYourFriendInfo(Girl &frn);
};


class Girl {
private:
	char phNum[20];
public:
	Girl(char* num) {
		strcpy(phNum, num);
	}
	void ShowYourFriendInfo(Boy &frn);
	friend class Boy;
};

void Boy::ShowYourFriendInfo(Girl &frn) {
	cout << "Her phone number : " << frn.phNum << endl;
}

void Girl::ShowYourFriendInfo(Boy &frn){
	cout << "His height : " << frn.height << endl;
}

int main(void) {
	Boy boy(170);
	Girl girl("010-2222-3333");
	boy.ShowYourFriendInfo(girl);
	girl.ShowYourFriendInfo(boy);
	return 0;
}

아마 따로 소스 설명을 하지 않아도 될 듯 하다.

그럼 friend 선언을 언제하는지 알아보자

사실 friend 선언은 C++에서 큰 논란이 있었던 것이다. 왜냐하면 정보은닉을 무너뜨려 버리기 때문이다.

따라서 friend 선언에 있어서는 지나치면 매우 위험하기 때문에, 필요한 상황에 소극적으로 사용해야 한다.

이왕이면 가급적 사용하지 않는 연습을 하자. 그래도 좋은 점은 연산자 오버로딩에서 볼 수 있는데, friend 선언은 자신이 충분히 이해했다라는 생각이 들 때 접근하도록 하자.


함수의 friend 선언


전역 함수를 대상으로도, 클래스의 멤버함수를 대상으로도 friend 선언은 가능하다. 물론 friend로 선언된 함수는 자신이 선언된 클래스의 private 영역에 접근이 가능하다.

#include<iostream>
using namespace std;

class Point;

class PointOP {
private:
	int opcnt;
public:
	PointOP() : opcnt(0) {}

	Point PointAdd(const Point&, const Point&);
	Point PointSub(const Point&, const Point&);
	~PointOP() {
		cout << "Operation times : " << opcnt << endl;
	}
};

class Point {
private:
	int x; 
	int y;
public:
	Point(const int &xpos, const int &ypos) : x(xpos), y(ypos) {}
	friend Point PointOP::PointAdd(const Point&, const Point&);
	friend Point PointOP::PointSub(const Point&, const Point&);
	friend void ShowPointPos(const Point&);
};

Point PointOP::PointAdd(const Point& pnt1, const Point& pnt2) {
	opcnt++;
	return Point(pnt1.x + pnt2.x, pnt1.y + pnt2.y);
}

Point PointOP::PointSub(const Point& pnt1, const Point& pnt2) {
	opcnt++;
	return Point(pnt1.x - pnt2.x, pnt1.y - pnt2.y);
}

int main(void) {
	Point pos1(1, 2);
	Point pos2(2, 4);
	PointOP op;

	ShowPointPos(op.PointAdd(pos1, pos2));
	ShowPointPos(op.PointSub(pos2, pos1));
	return 0;
}

void ShowPointPos(const Point& pos) {
	cout << "x : " << pos.x << ", ";
	cout << "y : " << pos.y << endl;
}

위 예제를 잘 보고 전역함수와 멤버함수에 대한 friend 선언방법을 이해하도록 하자.

friend void ShowPointPos(const Point&);

참고로 다음 선언에서 friend 다음에 원형을 선언하고 있기 때문에 굳이 friend 앞에 한번 더 원형을 써줄 필요는 없다.

이 다음은 static에 대해서 쓸 것이다. static, const 등의 비교는 면접 질문에도 자주 나오고 상당히 중요한 개념이기에 정확히 알아 두는 것이 좋다.

오늘 포스팅은 길어졌으므로 다음 포스팅에 정리하도록 하겠다.

Blog Logo

구찌


Published

Image

구찌의 나도한번 해블로그

구찌의 개발 블로그 입니다.

Back to Overview