Binary operator, as Global Function
◆ 이항(binary) 연산자의 전역 함수로의 선언 (+, -, ==,!=, > , < , etc)
● Operator 오버로딩을 전역 함수로 선언(Point::operator 가 아님!)
● Lhs 도 매개변수로써 전달
☞ 이러한 구현을 위해서는 함수를 friend로 선언하는 것이 일반적
Point operator+(const Point& lhs, const Point& rhs);
Point operator-(const Point& lhs, const Point& rhs);
Point operator==(const Point& lhs, const Point& rhs);
Point operator<(const Point & lhs, const Point & rhs);
Point p1{ 10,20 };
Point p2{ 30,40 };
Point p3 = p1 + p2; // operator+(p1,p2) or p1.operator+(p2)
p3 = p1 - p2; // operator-(p1,p2) or p1.operator-(p2)
if(p1==p2) // operator==(p1,p2) or p1.operator==(p2)
...
이러한 명령문을 작성하면, 컴파일러는 사실 오른쪽 주석 두 개 후보 중 호출 가능한 것을 찾습니다.
✅ 이항 연산자의 전역 함수 오버로딩 정리 (+ 강의자료 통합)
🔷 전역 함수 방식이란?
이항 연산자 오버로딩을 멤버 함수가 아닌, 전역 함수(비멤버) 로 정의하는 방법.
Point operator+(const Point& lhs, const Point& rhs);
이 방식에서는 lhs, rhs 모두 함수의 인자로 들어오며,
클래스 외부 함수에서 구현된다
🧠 왜 전역 함수로 오버로딩할까?
✅ 교환 법칙을 구현할 수 있기 때문!
Point p1{10, 20};
Point p2{30, 40};
Point p3 = p1 + p2; // OK: 멤버 함수 or 전역 함수
Point p4 = p2 + p1; // OK: 전역 함수이면 좌우 위치 무관
멤버 함수는 좌측 피연산자만 해당 클래스 객체여야 작동
하지만 전역 함수는 lhs, rhs 모두 자유롭게 설계 가능
📌 전역 함수의 일반적 형식
Point operator+(const Point& lhs, const Point& rhs);
Point operator-(const Point& lhs, const Point& rhs);
bool operator==(const Point& lhs, const Point& rhs);
bool operator!=(const Point& lhs, const Point& rhs);
bool operator<(const Point& lhs, const Point& rhs);
모두 인자를 2개 받으며, 멤버 함수가 아니라는 점이 중요!
❗ private 멤버 접근 문제
전역 함수에서는 클래스의 private 멤버(xPos, yPos)에 직접 접근할 수 없다.
Point operator+(const Point& lhs, const Point& rhs) {
return Point{lhs.xPos + rhs.xPos, lhs.yPos + rhs.yPos}; // ❌ 에러
}
🔐 해결법: friend 선언
class Point {
private:
int xPos;
int yPos;
public:
friend Point operator+(const Point& lhs, const Point& rhs);
};
friend로 선언하면 전역 함수가 클래스의 private 멤버에도 접근 가능해진다.
⚙️ 컴파일러가 어떤 함수를 선택하나?
Point p3 = p1 + p2;
이 문장이 있으면 컴파일러는 다음 2가지 후보를 모두 찾아본다:
- p1.operator+(p2) (멤버 함수)
- operator+(p1, p2) (전역 함수)
💡 둘 중 하나라도 있으면 호출 가능
둘 다 있으면 →오버로드 우선순위에 따라 선택 (보통 멤버 함수 우선)
🧩 namespace 관련 질문 정리
“전역 함수인데 namespace가 들어가면 애매하다”는 말은?
- 전역 함수라 해도 namespace 안에 있으면 진짜 ‘전역(global)’이 아님
- 예: my::operator+는 using namespace my; 없이 p1 + p2에 바로 적용되지 않음
- 그래서 “전역 함수”라고 할 때는 사용 범위가 열려 있다는 느낌으로 이해하면 된다.
⚠️ 잘못된 해결법: public으로 풀기?
class Point {
public:
int xPos; // ❌ 은닉 위반
};
- 이렇게 하면 전역 함수에서 접근은 가능하지만
- 객체지향의 캡슐화, 정보은닉 원칙 위배
- ⇒ 이러면 “그냥 구조체 쓰지 왜 클래스를 쓰냐?”는 소리 나옴
✅ 핵심 요약
구분 | 멤버 함수 오버로딩 | 전역 함수 오버로딩 |
피연산자 수 | 1개 (this + 1 인자) | 2개 모두 인자로 받음 |
위치 제약 | 좌측 피연산자가 클래스 객체여야 함 | 없음 (교환 가능) |
private 접근 | 직접 가능 | friend 선언 필요 |
사용 예 | p1 + p2 (p1이 클래스) | 3 + p1, p1 + 3 등 자유롭게 |
✅ 결론
전역 함수 방식의 연산자 오버로딩은 멤버 함수 방식보다 피연산자의 자유도가 높으며,
특히 3 * p1 같은 표현이나 교환 법칙 구현이 필요한 상황에서 필수적이다.
단, private 멤버를 사용하는 경우에는 friend 선언을 통해 클래스 내부 접근 권한을 부여해야 한다.
중요한 Part이다.★★★★★
● +연산자의 오버로딩
friend Point operator+(const Point& lhs, const Point& rhs)
{
return Point{ lhs.xPos + rhs.xPos, lhs.yPos + rhs.yPos };
}
Point p3 = p1 + p2 ; // operator+(p1,p2);
friend : operator+ 함수는 Point 클래스의 멤버 함수가 아니기 때문에 xPos와 yPos에 쉽게 접근하기 위해 friend 선언합니다.
전역함수가 호출되었으므로, p1과 p2의 xPos , yPos를 더한 값을 가지고 있는 객체가 반환되고, 그 객체는 p3에 저장됩니다.
operator+ 함수는 Point 클래스의 멤버 함수가 아님을 다시 한번 말합니다.
✅ 강의 내용
operator+ 함수는 Point 클래스의 멤버 함수가 아니기 때문에
xPos, yPos에 쉽게 접근하려면 friend 선언이 필요하다.
🤔 궁금증
전역 함수인데 왜 클래스 안에 있을까?
클래스 안에 선언하면 멤버 함수 아닐까?
friend Point operator*() 이런 식으로 클래스 안에 정의된 함수는 멤버 함수 아닌가?
const를 뒤에 못 붙이는 것도 헷갈린다.
🧠 해설
📌 Q1. 클래스 안에 있는 friend 함수는 멤버 함수인가?
❌ 아니다. 전역 함수다.
class Point {
friend Point operator+(const Point& lhs, const Point& rhs); // 전역 함수임
};
- 클래스 내부에 정의돼 있어도, this가 없고 인자 2개를 직접 받는다.
- 단지 Point 클래스에 접근 권한을 가진 전역 함수일 뿐이다.
- "나 멤버 함 아님!"이라고 friend가 직접 말해주는 구조
📌 Q2. const를 뒤에 못 붙이는 이유는?
Point operator*(int scale, const Point& rhs) const; // ❌ 전역 함수에서는 컴파일 에러
- 뒤에 붙는 const는 멤버 함수 전용 문법
- 의미: "내부 멤버 안 바꿀게요!"
- 전역 함수에는 this가 없기 때문에 붙일 의미 자체가 없어
- 그래서 전역 함수는 const를 인자에만 쓸 수 있고, 함수 자체엔 못 붙임
✅ 강의 내용
Point p3 = p1 + p2; 처럼 썼을 때, 컴파일러는
1. p1.operator+(p2)
2. operator+(p1, p2)
둘 중 호출 가능한 걸 찾는다.
🤔 궁금증
둘 다 있으면 어떤 게 우선으로 호출될까?
전역 함수 먼저 쓰면 멤버 함수는 무시될까?
🧠 해설
📌 Q3. 멤버 vs 전역 함수 중 누가 먼저?
- 멤버 함수가 있으면 우선적으로 사용된다
- 없으면 전역 함수로 넘어감
// 1순위
Point Point::operator+(const Point& rhs) const;
// 2순위
friend Point operator+(const Point& lhs, const Point& rhs);
📌 그래서 실무에서는 멤버 함수로 충분하면 그것만 쓰고,
교환법칙이 필요한 경우 (int + Point)는 전역 함수로 보완하는 방식이 일반적이다.
✅ 설명
friend를 쓰지 않으면 xPos, yPos에 접근할 수 없다.
public으로 만들면 정보 은닉이 깨진다.
🤔 궁금증
private이라 접근 안 된다는 게 무슨 말일까?
객체로는 접근이 불가능하다는 표현이 헷갈린다.
🧠 해설
📌 Q4. 전역 함수에서 private 멤버 접근 못 하는 이유?
Point operator+(const Point& lhs, const Point& rhs) {
return Point{ lhs.xPos + rhs.xPos, lhs.yPos + rhs.yPos }; // ❌ 오류
}
- 클래스 바깥에서는 private 멤버를 직접 접근할 수 없음
- 객체로는 접근 불가능하다라는 표현보단
👉 "클래스 외부에서는 private 멤버에 접근할 수 없다"라고 이해해야 한다.
📚 정리 요약 (학습노트용)
질문 | 답변 |
friend 함수가 멤버 함수인가요? | ❌ 전역 함수지만 private 접근 권한을 부여받음 |
클래스 안에 있어도 멤버 함수인가요? | ❌ 인자를 다 받고 this가 없으면 전역 함수 |
const Point& 인자는 왜 쓰나요? | 값 보호 + 복사 비용 줄이기 |
전역 함수 뒤에 const 못 붙이나요? | ✅ 멤버 함수만 가능, 전역 함수는 의미 없음 |
p1 + p2 쓸 때 컴파일러는 뭘 먼저 찾나요? | 멤버 함수 → 전역 함수 순서 |
전역 함수에서 private 멤버 접근하려면? | friend 선언 필요 |
public으로 바꾸면 안 되나요? | ❌ 정보 은닉 위배, 객체지향 원칙 깨짐 |
● Friend 유의사항
class Point {
private:
int xPos;
int yPos;
public:
Point(int x, int y)
:xPos{ x },yPos{y}
{ }
friend Point operator+(const Point& lhs, const Point& rhs)
{
return Point{ lhs.xPos + rhs.xPos, lhs.yPos + rhs.yPos };
}
};
operator+ 함수 코드가 class Point {...}; 안에 있다고 해서 무조건 멤버 함수가 아니다.
class Point {
private:
int xPos;
int yPos;
public:
Point(int x, int y)
:xPos{ x },yPos{y}
{ }
friend Point operator+(const Point& lhs, const Point& rhs)
{ }
};
Point operator+(const Point& lhs, const Point& rhs)
{
return Point{ lhs.xPos + rhs.xPos, lhs.yPos + rhs.yPos };
}
위에 코드는 단지 왼쪽의 코드를 줄여 쓴 것뿐이다. operator+는 전역 함수이고, class는 이 함수를 friend 선언한 것을 축약해서 쓴 것이다.
📌 friend 전역 함수는 멤버 함수가 아니다
Point 클래스 내부에 다음과 같이 작성된 friend 함수가 있다고 하더라도, 이는 멤버 함수가 아니다.
이 함수는 Point 클래스 외부에 정의되는 전역 함수이며,
단지 friend 키워드를 통해 Point 클래스의 private 멤버(xPos, yPos)에 접근할 수 있는 접근 권한만 부여받은 것이다.
클래스 내부에 선언되어 있더라도 this 포인터를 가지지 않기 때문에 멤버 함수로 간주되지 않는다.
즉, 이 함수는 클래스의 멤버 함수가 아닌, 전역 함수(free function)이다.
● +연산자의 오버로딩 동작 방식의 이해
friend Point operator+(const Point& lhs, const Point& rhs)
Point p3 = p1 + p2; // operator+(p1,p2)
...
ChatGPT 가 만든 Point_asGlobal_Binary_Plus.cpp 예제 코드
<< 연산자도 추가하였다.
#include <iostream>
// Point 클래스 정의
class Point {
private:
int xPos;
int yPos;
public:
// 생성자
Point(int x = 0, int y = 0)
: xPos{x}, yPos{y} {}
// operator+ 연산자 friend 선언
friend Point operator+(const Point& lhs, const Point& rhs);
// operator<< 출력 연산자 friend 선언
friend std::ostream& operator<<(std::ostream& os, const Point& pt);
};
// 전역 함수로 + 연산자 오버로딩
Point operator+(const Point& lhs, const Point& rhs) {
return Point{ lhs.xPos + rhs.xPos, lhs.yPos + rhs.yPos };
}
// 전역 함수로 << 출력 연산자 오버로딩
std::ostream& operator<<(std::ostream& os, const Point& pt) {
os << "(" << pt.xPos << ", " << pt.yPos << ")";
return os;
}
int main() {
Point p1{10, 20};
Point p2{30, 40};
// + 연산자: 전역 함수
Point p3 = p1 + p2;
// << 연산자: 전역 함수
std::cout << "p1 = " << p1 << "\n";
std::cout << "p2 = " << p2 << "\n";
std::cout << "p1 + p2 = " << p3 << "\n";
return 0;
}
📌 설명 요약
연산자 | 구현 방식 | 반환형 | 설명 |
+ | 전역 함수 | Point | 두 점 좌표 덧셈 |
<< | 전역 함수 | std::ostream& | 객체 상태를 출력 스트림에 전달 |
✅ std::ostream로 선언해야 하는 이유
🔹 1. << 연산자는 ostream 객체의 연산자 오버로딩 함수이다
C++에서 cout << obj;와 같이 사용할 때,
실제로는 다음과 같은 함수 호출로 해석된다:
operator<<(std::ostream& os, const YourType& obj)
즉, << 연산자는 std::ostream 타입의 첫 번째 인자를 받는 전역 함수 형태로 오버로딩되어 있어야 한다.
왜냐하면 cout은 std::ostream 클래스의 객체이고,
사용자 정의 타입(Point)을 출력하기 위해서는 ostream에 맞는 함수 시그니처를 가져야 하기 때문이다.
🔹 2. std::ostream은 C++ 표준 라이브러리의 클래스이다
- ostream은 std 네임스페이스에 정의되어 있음
- 따라서 정확한 타입을 명시하려면 std::ostream을 써야 한다
namespace std {
class ostream {
...
};
}
🔹 3. using namespace std;를 쓰면 어떻게 되나?
using namespace std;
- 이 선언을 하면 코드 전체에서 std::를 생략해도 됨
- 따라서 std::ostream 대신 단순히 ostream이라고 써도 문법적으로는 문제없다
ostream& operator<<(ostream& os, const Point& pt); // 가능
🔸 그럼에도 불구하고 std::ostream을 명시하는 이유
- 명시적인 코드가 가독성이 더 좋다
→ 이 함수가 명확히 C++ 표준 라이브러리 타입을 다룬다는 걸 보여줌 - 헤더 파일에서 using namespace std;는 지양됨
→ 특히 라이브러리/공용 코드에선 네임스페이스 충돌 위험이 있으므로
std::를 명시하는 것이 더 안전하고 명확한 코드 작성법이다 - 협업 코드/오픈소스에선 std 명시가 관례적
💡 따라서, 항상 std::ostream을 명시하는 형태가 더 좋다.
📌 결론 요약
질문 | 답변 |
왜 std::ostream을 써야 하나? | ostream은 std 네임스페이스 안에 있는 타입이기 때문에 명시해야 한다 |
using namespace std; 쓰면 ostream만 써도 되나? | 문법적으로는 가능하지만, 명확성과 충돌 방지를 위해 std::ostream을 사용하는 것이 바람직하다 |
출력 연산자 오버로딩 시 함수 시그니처는? | std::ostream& operator<<(std::ostream& os, const Point& pt); |
std::ostream은 C++ 표준 라이브러리에 정의된 출력 스트림 타입이다. 출력 연산자 <<를 오버로딩할 때는, 첫 번째 인자로 반드시 std::ostream&을 받아야 cout << obj;와 같은 구문이 동작할 수 있다. using namespace std;를 선언하면 ostream으로 줄여 쓸 수는 있지만, 코드의 명확성과 충돌 방지를 위해 std::ostream을 직접 명시하는 것이 바람직하다.
● * 연산자의 구현
☞ 3*p1도 가능하도록 하기 위해 전역 함수를 추가 구현
● 마찬가지로, 함수의 friend를 가정함
☞ friend 가 아닌 경우 lhs.xPos , rhs.xPos 접근 불가
Point Point::operator*(int scale, const Point& rhs)
{
return Point{ xPos * scale, yPos * scale };
} // p1*3 이 진입하는 멤버 함수
friend Point operator*(int scale, const Point& rhs)
{
return rhs * scale;
} // 3*p1 이 진입하는 전역 함수
✅ 연산자 오버로딩의 교환법칙 구현
Point p3 = 3 * p1;와 같이 왼쪽 피연산자가 int 타입인 경우,
이는 3.operator*(p1) 형태로는 해석할 수 없기 때문에 문법적으로 성립하지 않는다.
이를 해결하려면 다음과 같은 전역 함수를 만들어야 한다:
Point operator*(int scale, const Point& rhs);
이 전역 함수는 3 * p1에서 scale = 3, rhs = p1로 해석되어 호출이 가능하다.
✅ 클래스 코드 예시
class Point {
private:
int xPos;
int yPos;
public:
Point(int x=0, int y=0)
: xPos{x}, yPos{y} {}
// 멤버 함수
Point operator*(int scale) const {
return Point{ xPos * scale, yPos * scale };
}
// friend 전역 함수 (좌우 순서 자유롭게)
friend Point operator*(const Point& rhs, int scale);
friend Point operator*(int scale, const Point& rhs);
};
// 전역 함수 정의
Point operator*(const Point& rhs, int scale) {
return Point{ rhs.xPos * scale, rhs.yPos * scale };
}
Point operator*(int scale, const Point& rhs) {
return Point{ rhs.xPos * scale, rhs.yPos * scale };
}
이와 같이 구현하면 p1 * 3, 3 * p1 모두 정상적으로 작동하며,
사용자는 연산자 위치를 기억할 필요 없이 교환법칙처럼 자연스럽게 사용할 수 있다.
✅ 컴파일러가 모호성을 판단하는 예시
Point p3 = p1 * 3;
이 표현은 내부적으로 두 가지 해석이 동시에 가능하다:
- p1.operator*(3) (멤버 함수)
- operator*(p1, 3) (전역 함수)
이 경우 컴파일러는 두 함수 모두 호출 조건을 만족하므로
"‘operator *’ is ambiguous" (모호하다)**는 컴파일 에러를 발생시킨다.
❓friend 함수에 바디를 클래스 안에 써도 돼? 현업에서도 그렇게 쓰는지?
답변:
C++ 문법상 friend 전역 함수는 클래스 내부에 바디까지 작성해도 문제없다.
하지만 현업이나 팀 프로젝트에서는 클래스 정의와 구현을 분리하는 것이 일반적이다.
보통은 클래스 안에는 다음처럼 선언만 넣고,
class Point {
friend Point operator*(int scale, const Point& rhs);
};
바디는 .cpp 파일이나 클래스 외부에 따로 정의하는 것이 일반적이다.
즉, 클래스 안에 정의까지 쓰는 건 예제나 교육 자료에서 지면을 아끼기 위한 경우가 많다.
❓ 멤버 함수가 먼저 호출된다고 했는데, 지금 에러 보면 아닌 것 같아. 틀린 거 아닌가?
답변:
컴파일러는 멤버 함수 우선으로 탐색하지만,
전역 함수가 동시에 정확히 같은 시그니처로 호출 가능할 경우,
오버로딩 해석에서 모호성(ambiguity) 에러를 발생시킨다.
즉, “멤버 함수 먼저 본다 → 둘 다 가능하면 멈추지 않고 둘 다 비교 → 충돌 시 에러”가 맞는 흐름이다.
둘 중 하나는 반드시 제거해야 한다.
🧷 요약 문장
- 3 * p1 형태는 int 타입에는 멤버 함수가 없기 때문에 operator*(3, p1) 형태의 전역 함수로만 처리할 수 있다.
- 전역 함수와 멤버 함수가 동시에 존재하고 호출 조건이 중복되면, 컴파일러는 모호성 에러를 발생시킨다.
- 이를 방지하려면 같은 의미의 연산자 오버로딩은 하나의 방식으로만 구현하는 것이 안전하다.
- friend 전역 함수는 클래스 내부에 정의해도 되지만, 실무에서는 보통 클래스 외부에 정의한다.
ChatGPT 가 만든 Point_asGlobal_Binary_mul_And_Unary_Increment.cpp
#include <iostream>
class Point {
private:
int xPos;
int yPos;
public:
// 생성자
Point(int x = 0, int y = 0)
: xPos{x}, yPos{y} {}
// 전위 ++ 연산자 (단항, 멤버 함수로 구현)
Point& operator++() {
++xPos;
++yPos;
return *this;
}
// 출력 연산자 friend 선언
friend std::ostream& operator<<(std::ostream& os, const Point& pt);
// 전역 operator* 사용을 위한 friend 선언
friend Point operator*(const Point& lhs, int scale);
friend Point operator*(int scale, const Point& rhs);
};
// 전역 함수: 출력 연산자 <<
std::ostream& operator<<(std::ostream& os, const Point& pt) {
os << "(" << pt.xPos << ", " << pt.yPos << ")";
return os;
}
// 전역 함수: Point * int
Point operator*(const Point& lhs, int scale) {
return Point{ lhs.xPos * scale, lhs.yPos * scale };
}
// 전역 함수: int * Point
Point operator*(int scale, const Point& rhs) {
return Point{ rhs.xPos * scale, rhs.yPos * scale };
}
int main() {
Point p1{3, 4};
Point p2 = p1 * 2; // 전역 operator*(Point, int)
Point p3 = 2 * p1; // 전역 operator*(int, Point)
std::cout << "p1 = " << p1 << "\n";
std::cout << "p2 = " << p2 << "\n";
std::cout << "p3 = " << p3 << "\n";
++p1; // 전위 ++ 연산자
std::cout << "After ++p1: " << p1 << "\n";
return 0;
}
📌 설명 요약
항목 | 구현 방식 | 설명 |
Point * int | 전역 함수 | 좌측 피연산자가 Point |
int * Point | 전역 함수 | 좌측 피연산자가 int |
++p1 | 멤버 함수 | 단항 전위 연산자 오버로딩 |
<< | 전역 함수 | 출력 스트림 오버로딩 |
🧠 해설 문장
- 이항 곱셈 연산자 *를 전역 함수로 오버로딩하면 p * 3뿐 아니라 3 * p와 같이 좌우 피연산자 위치가 달라도 연산이 가능하다.
- 출력 연산자 <<는 std::ostream 타입의 첫 번째 인자를 받는 전역 함수로 정의해야 cout << obj 형태로 출력이 가능하다.
- 전위 증감 연산자 ++p1는 멤버 함수로 정의하며, 객체 자체를 변경하고 참조를 반환하는 방식으로 구현한다.
Operator Overloading
◆ 연산자 오버로딩 : 클래스에 대한 연산자의 적용 방식을 사용자가 직접 오버로딩하여 구현할 수 있다.
◆ 멤버 한수인 연산자 오버로딩 : 클래스의 멤버함수로 operatorX()라는 이름을 갖는 함수를 구현하여 연산자를 오버로딩 할 수 있다. 이때 이항 연산자의 경우 우측 피연산자는 인자로 넘어온다.
◆ 전역 함수인 연산자 오버로딩 : 멤버 함수로 구현 시 교환 법칙 문제가 발생할 수 있고, 이러한 경우 전역 함수로 오버로딩하며, 이때 friend 키워드를 사용하면 편리함
◆ 스트림 삽입 및 추출 연산자 오버로딩
◆ 대입 연산자 오버로딩
◆ 첨자 연상자 오버로딩
🔹 멤버 함수로만 구현할 때의 한계
- 연산자 오버로딩을 멤버 함수로만 구현하면 다음과 같은 문제가 발생할 수 있다.
Point p1{10, 20};
Point p2 = p1 * 3; // 가능 (p1.operator*(3))
Point p3 = 3 * p1; // 오류 (3.operator*(p1)은 불가능)
- 3은 리터럴이자 기본형(int) 타입이므로, 멤버 함수가 존재하지 않는다.
- 따라서 3 * p1과 같이 사용하면 컴파일러는 멤버 함수를 찾지 못하고 에러를 발생시킨다.
🔹 이 문제를 "교환 법칙 문제"라고 부른다
- 수학적으로 p1 * 3과 3 * p1은 같은 결과를 기대하지만,
- C++에서는 양쪽 피연산자의 타입에 따라 다르게 동작하므로,
- 실제 코드에서 두 표현이 모두 성립하지 않는 경우를 교환 법칙 문제가 있다고 말한다.
🔹 해결 방법: 전역 함수 + friend 키워드
- 전역 함수로 operator*를 오버로딩하면 3 * p1, p1 * 3 둘 다 구현 가능하다.
- 그러나 operator*는 클래스 외부에서 정의되므로 private 멤버 변수에 접근할 수 없다.
- 이때 가장 흔하게 쓰이는 방법이 friend 함수로 선언하여 접근을 허용하는 방식이다.
❓friend 를 쓰지 않고 private 멤버에 접근하지 않고 구현하는 방법은 없을까?
✔ 답변:
1. public 접근자(getter)를 제공하는 방법
class Point {
private:
int xPos;
int yPos;
public:
Point(int x = 0, int y = 0) : xPos{x}, yPos{y} {}
int getX() const { return xPos; }
int getY() const { return yPos; }
};
Point operator*(int scale, const Point& rhs) {
return Point{ rhs.getX() * scale, rhs.getY() * scale };
}
- 이 방식은 friend를 사용하지 않고도 외부에서 멤버 변수 값을 안전하게 접근할 수 있게 해준다.
- getX(), getY()를 통해 간접 접근이 가능하다.
- 하지만 단점은 코드가 다소 길어지고, 호출 비용이 추가된다는 점이다.
2. public 으로 멤버 변수를 노출하는 방법 → ❌ 비권장
// 나쁜 예
class Point {
public:
int xPos;
int yPos;
};
- 이 방법은 간단하지만, 정보 은닉 원칙에 위배된다.
- 외부에서 무분별하게 멤버 값을 수정할 수 있으므로 객체지향 설계 철학에 맞지 않는다.
✅ 결론
- friend를 남용하면 캡슐화가 깨질 수 있지만,
- 출력 연산자나 교환 법칙 문제 해결 같은 정당한 상황에서는 사용하는 것이 일반적이다.
- 객체지향 원칙을 지키면서 구현하려면 getter를 사용하는 방식이 가장 타협적인 방법이다.
전역 함수로 연산자 오버로딩을 구현하는 경우 private 멤버에 접근할 수 없다. 이때 friend 키워드를 사용하면 전역 함수에서도 클래스 내부 멤버에 접근이 가능하다. 하지만 friend 사용을 지양하고 싶다면, getter를 만들어서 안전하게 값을 접근할 수 있도록 구성할 수도 있다. 이 방식은 객체지향 원칙을 지키는 대안이 될 수 있다.
https://youtu.be/s_UdO2fofSw?si=me-AdLtxgGHDC1qo
'C++ > C++ : Study' 카테고리의 다른 글
8. 연산자 오버로딩 (6) - 스트림 삽입 및 추출 연산자 오버로딩 (1) | 2025.06.18 |
---|---|
8. 연산자 오버로딩 (5) - Ostream 객체 (0) | 2025.06.16 |
8. 연산자 오버로딩 (3) - 단항 연산자 오버로딩 멤버 함수로의 구현 (0) | 2025.06.10 |
8. 연산자 오버로딩 (2) - 이항 연산자 오버로딩 멤버 함수로서의 구현 (0) | 2025.06.09 |
8. 연산자 오버로딩 (1) - 소개 (3) | 2025.06.08 |