상세 컨텐츠

본문 제목

C++ 프렌드

C++/C++98

by deulee 2023. 8. 5. 11:21

본문

private 멤버 변수나 함수들은 객체의 신뢰성을 높이고 기능 개선도 용이한 건 맞다. 하지만 이 때문에 불편한 면이 있는 것은 부정할 수 없는 사실이다.

 

C++은 정보를 한번 숨기면 정상적인 문법으로는 외부에서 이 멤버를 참조할 수 없다. (비정상적으로는 가능)

 

이러한 불편함을 해소하기 위해 예외적으로 지정한 대상에 대해서 모든 멤버를 공개하는 기능이 있는데 이를 프렌드라고 한다.

 

프렌드 함수

프렌드는 다음의 세가지 종류에 지정할 수 있다.

 

1. 전역 함수

2. 클래스

3. 멤버 함수

 

상대적으로 간단한 프렌드 함수를 알아보도록 하자.

 

프렌드로 지정하고 싶은 함수의 원형을 클래스 선언문에 적되 원형앞에 friend라는 키워드를 붙인다.

class FriendC
{
	friend void func();
}

이제 위의 예제에서 func 함수는 클래스 선언부에 원형이 포함되어 있지만 FriendC 클래스의 멤버는 아니며 본체는 외부에 따로 존재하므로 전역 함수라고 보는 것이 옳다.

 

하지만 FriendC 클래스 선언부에서 func 함수를 프렌드로 지정했기 때문에 이 클래스의 멤버 함수인것처럼 동작한다. 즉, 이 클래스의 모든 멤버를 자유롭게 사용 가능하다는 것이다.

 

#include <iostream>

class Date;
class Time
{
	friend void OutToday(Date&, Time&);
private:
	int hour, min, sec;
public:
	Time(int h, int m, int s) : hour(h), min(m), sec(s)
	{}
};

class Date
{
	friend void OutToday(Date&, Time&);
private:
	int year, month, day;
public:
	Date(int y, int m, int d) : year(y), month(m), day(d)
	{}
};

void OutToday(Date& d, Time& t)
{
	std::cout << d.year << d.month << d.day << t.hour << t.min << t.sec << std::endl;
}

int main(void)
{
	Date D(2023, 8, 5);
	Time T(10, 2, 30);

	OutToday(D, T);
	return 0;
}

 

위의 예제에서 OutToday 함수는 이 두 클래스의 멤버 함수인 것처럼 private으로 인해 숨겨진 멤버를 자유롭게 액세스 할 수 있게 된다.

 

프렌드 클래스

만일 함수 하나가 아니라 클래스 자체를 프렌드로 지정하게 되면 어떻게 될까?

 

두 클래스가 서로 밀접한 관계를 가지고 있고 서로 숨겨진 멤버를 자유롭게 돌아다녀야 하는 상황이면 이와 같은 상황을 사용할 수 있을 것이다.

 

#include <iostream>

class Time
{
	friend class Date;
private:
	int hour, min, sec;
public:
	Time(int h, int m, int s) : hour(h), min(m), sec(s)
	{}
};

class Date
{
	friend class Time;
private:
	int year, month, day;
public:
	Date(int y, int m, int d) : year(y), month(m), day(d)
	{}
	void OutToday(Time& t)
	{
		std::cout << year << month << day << t.hour << t.min << t.sec << std::endl;
	}
};


int main(void)
{
	Date D(2023, 8, 5);
	Time T(10, 2, 30);

	D.OutToday(T);
	return 0;
}

이렇게 함으로써 Date는 Time의 프렌드 클래스로 지정이 되었으며, Date 객체는 Time 클래스의 모든 멤버에 접근 권한을 가지게 된다.

 

프렌드 멤버 함수

프렌드 클래스 지정은 특정 클래스의 모든 멤버 함수들이 자신의 숨겨진 멤버를 마음대로 읽도록 허락할 수 있었다.

 

하지만 이렇게 될 경우 굳이 공개하고 싶지 않은 멤버 함수까지도 공개하게 되는 것이다.

 

프렌드 멤버 함수는 특정 클래스의 특정 멤버 함수만 프렌드로 지정하는 것이며 꼭 필요한 함수에 대해선만 숨겨진 멤버를 액세스 하도록 범위를 좁게 설정할 수 있는 장점이 있다.

 

맨 처음에 설명했던 프렌드 함수랑은 개념은 동일하지만 프렌드로 지정된 함수가 전역이 아니라 어느 특정 클래스에 속한 멤버 함수라는 것이 다르다.

class Some
{
	friend void Any::func(Some& S);
}

 

이렇게 함으로써 Any 클래스의 func 함수는 Some의 모든 멤버에 접근할 수 있게 된다.

 

#include <iostream>

class Time;
class Date
{
private:
	int year, month, day;
public:
	Date(int y, int m, int d) : year(y), month(m), day(d)
	{}
	void OutToday(Time& t);
};

class Time
{
	friend void Date::OutToday(Time& t);
private:
	int hour, min, sec;
public:
	Time(int h, int m, int s) : hour(h), min(m), sec(s)
	{}
};


void Date::OutToday(Time& t)
{
	std::cout << year << month << day << t.hour << t.min << t.sec << std::endl;
}


int main(void)
{
	Date D(2023, 8, 5);
	Time T(10, 2, 30);
	
	D.OutToday(T);
	return 0;
}

 

위의 예시에서 오직 Date의 멤버 함수인 OutToday는 Time 클래스의 모든 멤버에 접근할 수 있게 된다.

 

하지만 위에서 보듯이 멤버 함수를 프렌드로 지정하기 위해서는 선언 순서에 주의를 주어야 한다.

 

프렌드 멤버 함수는 프렌드로 지정되는 클래스 소속이며 대상 클래스를 인수로 전달받기 때문에 프렌드 지정을 포함하는 클래스를 먼저 선언하고 프렌드 멤버 함수를 포함한 클래스를 전방 선언해야 한다.

 

즉, 서로를 알 수 있도록 소개시켜주는 과정을 자연스럽게 해야 한다고 생각하면 된다.

주의할 점

1. 프랜드 지정은 단방향이다.

 

B가 A의 친구이라고 가정했을 때 B는 A의 모든 멤버를 액세스 할 수 있지만, 반대로는 안된다.

 

이게 가능하게 하기 위해서는 양쪽 모두 상대방을 프랜드로 지정해야 하며 이런 관계를 상호 프렌드라고 한다.

 

2. 프렌드의 프렌드는 인정하지 않는다.

 

B가 A의 프렌드고 C가 B의 프렌드라고 했을 때, C는 A의 멤버에 접근하지 못한다.

 

3. 복수의 대상을 한 번에 지정 못한다.

 

한 번에 하나씩 지정이 가능하다.

class A
{
	friend class B, C; // X
	friend class B;
	friend class C;
}

 

4. 프렌드 관계는 상속되지 않는다.

 

A가 B를 프렌드로 지정하면 B는 A를 액세스 할 수 있지만 B에서 파생된 클래스 D는 A의 프랜드가 아니므로 A에 접근할 수 없다.

 

비정상적 방법으로 액세스 접근하기

#include <iostream>

class Weak {
private:
	int num;
public:
	void setNum(const int& num)
	{
		this->num = num;
	}
	int getNum() const
	{
		return this->num;
	}
};

struct Hacker
{
	int num;
};

int main(void)
{
	Weak w;
	w.setNum(3);
	Hacker* hack = reinterpret_cast<Hacker *>(&w);
	hack->num = 5;
	std::cout << w.getNum() << std::endl;
	return 0;
}

위의 예시처럼 포인터라 캐스트를 이용하면 강제로 접근할 수 있기는 핟.

 

지금 보면 Weak의 인스턴스 w의 주소를 (Hacker *)의 주소로 바꾸고 내부의 값을 바꾸게 되었다.

 

그 결과 객체 w의 private 멤버 변수의 값이 바뀐 것을 확인할 수 있다.

 

하지만 이 방법은 절대 쓰이지 말아야 하므로 이렇게 우회하는 방법이 있는 것만 알아두면 된다.

 

출처

https://stackoverflow.com/questions/6717163/how-to-access-private-data-members-outside-the-class-without-making-friends

 

How to access private data members outside the class without making "friend"s?

I have a class A as mentioned below:- class A{ int iData; }; I neither want to create member function nor inherit the above class A nor change the specifier of iData. My doubts:- How to ac...

stackoverflow.com

http://www.soen.kr/lecture/ccpp/cpplec.htm

 

C/C++ 강좌

 

www.soen.kr

 

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

C++ 정적 멤버, static  (0) 2023.08.05
C++ this  (0) 2023.08.05
C++ 생성자, 파괴자, 복사 생성자, 복사 대입 연산자  (0) 2023.08.03
C++ 함수 오버로딩  (0) 2023.08.03
C++ 참조자(reference)  (0) 2023.08.03

관련글 더보기