안녕하세요? 김백일입니다.
고수님들이 보시기에는 허접한 내용이지만,
처음으로 C++을 배우시는 분들을 위해 간단한 강좌를 올립니다.
팁란에 쓰기는 뭣해서 여기다 올려봅니다.
다음은 'C++ 기초 플러스'(성안당)에 있는 내용을 정리한 겁니다.
------------------------------------------------------------------------------------
프렌드 함수는 보통 연산자 겹지정(operator overloading)을 위해 사용됩니다.
다음과 같은 2차원 벡터를 나타내는
Vector(std::vector가 아닙니다.)라는 클래스를 정의해보죠.
(다음은 'C++ 기초 플러스'(성안당)에 있는 내용을 정리한 겁니다.)
멤버 함수로, 두 벡터의 덧셈, 뺄셈인 +과 - 연산과
상수를 곱하는 * 연산을 정의한다면 다음과 같습니다.
class Vector
{
private:
double x, y;
public:
Vector(double _x, double _y) : x(_x), y(_y) {};
Vector(const Vector& v) { x = v.x; y = v.y; }
double GetX() { return x; }
double GetY() { return y; }
void SetX(double _x) { x = _x; }
void SetY(double _y) { y = _y; }
Vector operator+(const Vector& b) { return Vector(x + b.x, y + b.y); }
Vector operator-(const Vector& b) { return Vector(x - b.x, y - b.y); }
Vector operator*(double n) { return Vector(n * x, n * y); }
}
사용할 때는
Vector a(1.0, 2.0), b(3.0, 4.0),
sum = a + b, diff = a - b;
라고 하면, 컴파일러가 다음과 같이 바꿉니다.
sum = a.operator+(b), diff = a.operator-(b);
물론, sum은 (4.0, 6.0)은 diff는 (-2.0, -2.0)가 됩니다.
operator*()를 테스트하려면
Vector mul = a * 2.0;
이라고 쓰면,
mul = a.operator*(2.0);
가 되어 mul이 (2.0, 4,0)이 되는데요,
만약 다음과 같이
Vector mul = 2.0 * a;
라고 쓰면, 어떻게 될까요?
컴파일러가 다음과 같이 변형하게 되는데,
mul = 2.0.operator*(a);
물론, 컴파일 에러가 나게 됩니다.
이런 문제를 해결하려면,
operator*()를 멤버 함수가 아니라 다음과 같이 프렌드 일반 함수로 정의하면 됩니다.
class Vector {
private:
...
public:
...
friend Vector operator*(const Vector& a, double n);
friend Vector operator*(double n, const Vector& a);
}
// 이렇게 짧은 함수는 inline을 붙이면 성능 향상에 도움이 됩니다.
inline Vector operator*(const Vector& a, double n)
{
return Vector(a.x * n, a.y * n);
}
inline Vector operator*(double n, const Vector& a)
{
return Vector(n * a.x, n * a.y);
}
그러면
Vector mul = 2.0 * a;
가
Vector mul = operator*(2.0, a)
로 바뀌므로 문제 없이 컴파일됩니다.
이번에는 다른 문제를 생각해보죠.
연산자 겹지정을 할 때는, 멤버 함수 대신 프렌드 일반 함수를 쓰실 것을 권합니다.
operator+()와 operator-()의 경우도,
class Vector {
...
friend Vector operator+(const Vector& a, const Vector& b)
friend Vector operator-(const Vector& a, const Vector& b)
...
}
inline Vector operator+(const Vector& a, const Vector& b)
{
return Vector(a.x + b.x, a.y + b.y);
}
inline Vector operator-(const Vector& a, const Vector& b)
{
return Vector(a.x - b.x, a.y - b.y);
}
라고 쓰면, 대칭적인 코드가 되기 때문에 가독성이 더 좋습니다.
또다른 예를 들어보죠.
Vector를 출력하려면 어떻게 해야 할까요?
예를 들어 다음과 같이 멤버함수로 정의해보죠.
class Vector
{
...
void operator<<(ostream& os, const Vector& v);
...
};
inline void Vector::operator<<(ostream& os, const Vector& v)
{
os << "(" << x << "," << y << ")";
}
라고 한다면,
Vector v;
일때
cout << v;
라고 쓸 수는 없습니다.
cout.operator<<(v);
로 변형하면
cout::operator<<(const Vector& v)
가 호출되어야 하는데, 이런 함수는 정의되어 있지 않기 때문이죠.
그러므로, 대신
v << cout;
이라고 쓸 수 밖에는 없습니다. 상당히 어색하지요. -_-;
그래서 다음과 같이 프렌드 일반 함수로 정의합니다.
class Vector
{
...
friend ostream& operator<<(ostream& os, const Vector& v);
...
};
inline ostream& operator<<(ostream& os, const Vector& v)
{
os << "(" << x << "," << y << ")";
return os;
}
여기서 return 값을 ostream& 으로 한 것은 다음과 같이 연속해서 출력하는 경우를 위해서 입니다.
cout << v << "is vector." << endl;
ostream& 리턴값이 없다면,
cout << v;
라고 쓸 수 밖에는 없죠.
하여튼 여기서는
operator<<(cout, v)
가 호출되므로 문제없이 컴파일 됩니다.
friend 함수는 일반함수이므로 클래스의 캡슐화에 위배가 되지 않냐는 생각을 하실 수도 있지만,
그렇지 않습니다. 단지 멤버 함수를 다른 표현으로 바꾼 것에 불과합니다.
클래스 내부에서 (friend라는 키워드를 붙여서) 선언을 하지 않으면
쓸 수 없는 것은 멤버 함수와 마찬가지입니다.
다시 한 번 강조하지만, 연산자 겹지정을 할 때는 멤버 함수대신 프렌드 일반 함수를 사용하세요.
코드의 가독성도 높아지고, 코딩 에러도 줄일 수 있는 좋은 방법입니다.
|