Part2::Ch 02. 연산자 오버로딩- 05. 비트 연산자 오버로딩

2025. 5. 2. 12:33Programming Language/C++

반응형

우선 비트 연산자의 종류로는 ~(NOT)연산자, &(AND)연산자, |(OR)연산자, ^(XOR)연산자, <<(Left Shift)연산자, >>(Right Shift)연산자가 있다.

각각의 연산자에 대한 오버로딩에 대해서 한번 알아보도록 하자.

 

1. << 연산자

<< 연산자와 >> 연산자는 각각 std 에서 cout, cin과 같은 입출력 연산자로 사용했었는데 이 입출력 연산자로써 오버로딩을 한번 해보도록 하자.

 

먼저 우리가 이전에 만들었던 Vector 클래스를 cout으로 넘겨주는 연산을 한번 확인해보자.

먼저 Vector 클래스를 만들어주고

우리가 하고 싶은건 

이렇게 출력할 수 있도록 구현해보고자 하는것이다.

그러면 사실 cout이 좌항으로 cout.operator(v)가 되어야하는데 사실 cout은 표준 입출력이기 때문에 우리가 구현을 다시할 수 가 없기에 기존에 전역 함수로 만들었던 operator(cout, v)와 같은 구현을 해보도록 하자.

 

먼저 전역함수를 하나 만들어보는데 출력만 할것이니까 반환형은 void로 하고 함수를 만들어준다.

이때 cout의 타입을 한번 확인해보면 

std의 ostream타입임을 알 수 있다.

std::ostream은 복사가 아예 금지된 클래스로 무조건 &참조로 값을 던져야만 함

이렇게 매개변수를 넣어주자.

그리고 내부에는 그냥 os 를 사용해서 공백을 구분으로 해서 멤버 변수를 출력하는 문을 만들어주고

friend를 사용해서 프로토 타입을 class 내부에 넣어주자.

이렇게 하면 끝난 건데 호출 부를 다시보면

이렇게 에러가 나는 것을 볼 수 있다.

보면 사실 저 연산은 

std::cout << v << std::endl;
              ↓
operator<<(std::cout, v) << std::endl;

이것인데 operator<<는 반환형이 없는 void형태이기에 다음 << 연산을 처리할 수 없게 되는 것이다.

그래서 ostream을 다시 반환해줘야만 한다.

이제 실행시켜보면

이렇게 결과가 잘 나오는 것을 볼 수 있다.

보면 

이 둘은 동일하게 같은 연산이라는 것을 알 수 있다.

 

2. >> 연산자

이제 입력 연산자로써 >> 을 오버로딩 해보자.

먼저 cin이 무슨 타입인지 확인해보고 

istream타입이니 반환형은 void에 매개변수 첫 번째로는 istream객체를 참조로 받아주고 두번째 매개변수로는 Vector의 값을 변경해야 함으로 그냥 Vector객체를 참조값으로 받아주자.

그리고 내부에서 istream객체를 사용해서 Vector의 각 멤버 변수의 값을 입력해주고

마지막에 이 값을 cout을 통해서 출력해주자.

실행해보면

이렇게 입력을 잘 받는것을 볼 수있다.

 

만약 이 부분을 String으로 받은 후에 stoi로 하여 멤버 변수에 넣어주는 방법으로 구현도 가능하다.

물론 이때는 string을 include 해줘야 한다.

 

사실 강사는 이 방법으로 풀었는데 궂이 이렇게 구현해야하는가 싶은 의문이 있다.

타입에 대한 안정성을 위해서인가.. 싶기도 하다 내가 한건 뭔가 암시적 형변환을 일으켜 성능에 큰 차이를 줄지도 모른다는 생각이 들기도 한다..

 

알아보니 두번째 방법은 try-catch를 사용해서 이런 저런 입력 검증, 안정성등 확장성이 있는 상태로 확인됨.

 

왠만 하면 정석적으로 구현하는게 좋을 것 같기도 함...

 

저런 부분에서는 뭔가 C++하는 개발자로써의 머리는 아닌듯 싶음 난....

 

 

그 외 연산자의 경우는 사실 보면 간단하게 사용할것 같아서 그냥 코드만 짰다 그냥 보고 이해하셔도 이해할 정도로 쉬울것으로 보인다.

#include <iostream>
#include <string>

class Vector {
private:
	int x, y;
public:
	Vector(int x, int y)
		:x(x), y(y)
	{
		
	}

	friend std::ostream& operator<<(std::ostream& os, const Vector& v) {
		os << "Vector x : " << v.x << ", Vector y : " << v.y;
		return os;
	}

	friend std::istream& operator>>(std::istream& is, Vector& v) {
		std::string temp;
		is >> temp;
		v.x = stoi(temp);

		is >> temp;
		v.y = stoi(temp);
		return is;
	}

	Vector operator~() const {
		return Vector{ ~x, ~y };
	}

	Vector operator&(const Vector& v) const {
		return Vector{ x & v.x, y & v.y };
	}

	Vector operator|(const Vector& v) const {
		return Vector{ x | v.x, y | v.y };
	}

	Vector operator^(const Vector& v) const {
		return Vector{ x ^ v.x, y ^ v.y };
	}

	Vector operator<<(const int num) const {
		return Vector{ x << num, y << num };
	}

	Vector operator>>(const int num) const {
		return Vector{ x >> num, y >> num };
	}

	void print() {
		std::cout << "x: " << x << ", y: " << y << std::endl;
	}
};

int main() {
	Vector v{ 2, 2 };  // 0010
	Vector v0 = ~v;
	v0.print();

	Vector v1{ 6, 6 }; // 0110
	Vector v2 = v & v1;// 0010
	v2.print();

	Vector v3 = v | v1;
	v3.print(); // 0110

	Vector v4 = v ^ v1;
	v4.print(); // 0100

	Vector v5 = v << 1;
	v5.print(); // 0010 << 1 = 0100

	Vector v6 = v >> 1;
	v6.print(); // 0010 >> 1 = 0001
}

여기서 주의해야할 점은 나는 매개변수의 타입을 const Vector& 로 넣긴 했다만 매개변수의 타입을 Vector&로만 할 경우 문제가 발생할 수 있는데 아래를 보면 우리가 만들었던 <<출력 연산자에서 문제가 생겼다.

출력 연산자를 보면

매개변수의 형태중 Vector가 그냥 Vector& 참조 타입인데 ~연산자의 반환타입에서 보면

이 형태임을 알 수 있고 이를 종합적으로 

이 연산에서 확인해보면

이와 동일한 상태인건데 위 처럼 Vector{2, 2} 처럼 우항에서 단기적으로 생성되는 이름없는 값, 일회성 값에 대해서는 참조가 불가능하다.

 

이는 우항의 값이 저 line을 지나가면 사라지는 값이기에 참조가 불가능하고 컴파일러가 알리는 것이다.

이를 해결하기 위해서는 Vector의 참조 타입을 const로 변경해주면 컴파일러가 우항의 Vector의 값을 유지할 수 있도록 보장해준다.

그렇기에 여기서는 

이러한 형태가 되어야 하고 좌항에 해당하는 

이 매개변수의 타입이 

이렇게 const 타입이여만 한다는 점을 유의하자.

 

 

반응형