c++ 언어에는 기본 자료형에 대한 여러 가지 연산자가 정의되어 있다. 그러나 사용자가 정의한 자료형이나 클래스에 대한 경우는 연산자가 정의되어 있지 않는데 연산자 다중 정의는 c++언어에 정의되어 있는 연산자들을 사용자가 선언한 클래스에서 사용할 수 있도록 정의한다.
1. 단항 연산자의 다중 정의
단항 연산자는 피연산자를 하나만 갖는 연산자로서, ++나 --와 같은 연산자가 그 예시가 될 수 있다.
단항 연산자는 연산자가 피연산자 앞에 있는 것과 뒤에 있는 것이 있는데,
연산자를 앞에 사용하는 것을 전위 표기법이라 하고, 연산자가 뒤에 위치하고 있는 것을 후위 표기법이라고 한다.
단항 연산자는 전위, 후위 표기법에 따라 결과가 다르기에 구분하여 작성하는 것이 중요하다.
1) 전위 표기법
다음은 전위 표기법의 예시이다.
int a,b =10;
a= ++b;
++연산자는 b의 값을 1 증가시키고, 증가된 b의 값이 a의 대입되는 걸 볼 수 있다.
다음은 전위 표기 단항 연산자를 다중 정의하는 구문이다.
ReturnClass ClassName::operator opSymbol()
{
....
}
*opSymbol : ++,-- 등의 단항 연산기호
함수를 정의하는 것과 크게 다르지 않지만 operator라는 키워드와 연산자 기호를 사용한다는 점에서 차이가 있다.
2) 후위 표기법
int a,b =10;
a= b++;
다음처럼 연산자를 뒤에 기입하는 방법이다.
이 예에서는 ++연산자는 b의 값을 1 증가시키고 수식의 결과는 전위 표기법과 달리 변화되기 이전의 b의 값이 저장된다.
그러므로 a에는 10 , b에는 11이라는 값이 저장된다.
후기 표기법을 사용하는 연산자를 정의하는 구문은 다음과 같다.
ReturnClass ClassName::operator opSymbol(int)
{
....
}
여기서 형식 매개변수를 보면 이름 없이 단지 타입을 나타내고 있는데 이 의미는 이 연산자가 후위 표기법을 사용하는 단항 연산자임을 알려 주는 것이다.
2. 이항 산술 연산자 및 관계 연산자의 다중 정의
이항 연산자도 단항 연산자와 유사한 방법으로 다중 정의할 수 있다.
이항 연산자를 다중 정의하는 구문은 다음과 같다.
ReturnClass ClassName::operator opSymbol(ArgClass arg)
{
....
}
opSymbol은 2항 연산자 기호이고, arg는 연산자의 우측에 존재하는 ArgClass 타입의 피연산자이다.
3. 스트림 입출력 연산자의 다중 정의
#include <iostream>
using namespace std;
Class c;
cout << c <<endl;
c라는 객체를 출력하기 위해서 스트림 입출력 연산자를 저의 할 수 있다.
이때 << 의 좌측은 표준 출력 스트림을 나타내는 객체이다. 따라서 <<연산자를 Class에서 직접 다중 정의할 수 없음을 알 수 있다. 따라서 private 데이터 멤버를 반환하는 함수를 정의하든지, friend 키워드를 이용하는 방법을 사용할 수 있다.
class Example{
.....
public :
.....
friend ostream& operator<<(ostream& os, const Example& ex);
};
ostream클래스의 private 멤버 변수에 접근하기 위해 friend 키워드를 사용했다.
5. 대입 및 이동 대입 연산자
객체에 대한 기본적인 대입 연산은 객체의 데이터 멤버를 그대로 복사하는 방식으로 처리된다.
이러한 복사 방식은 동적으로 할당된 메모리를 가리키는 포인터 또한 값만 복사하므로 동일한 메모리를 가리키는 공유 상태가 되어 문제가 될 수 있다.
이러한 유형의 클래스에 대해서는 복사 생성자를 정의하는 것과 같은 이유로 대입 연산자를 정의할 필요가 있다.
대입 연산자의 다중 정의
ReturnClass ClassName::operator=(const ClassName& c)
{
delete[] 기존메모리 반환
new[] 새로운 메모리를 할당하고
memcpy(...)를 통해 데이터 복사
return *this;
}
이동 대입 연산자의 다중 정의
ReturnClass& ClassName::operator=(ClassName&& c){
자신의 동적 메모리 해제;
객체의 데이터멤버 복사
c의 동적메모리 복사
c.동적메모리포인터변수= nullptr;
return *this;
}
c는 임시 객체를 받을 수 있도록 r-value참조로 선언한 것을 볼 수 있다.
그 뒤 *this객체에 얕은 복사를 진행한 뒤 c의 포인터 변수를 nullptr를 대입함으로써 c의 내용이 *this객체에 이동되었다.
이동 대입 연산자를 사용하기 위해선 결국 = 연산자의 우측 피연산자가 r-value값이어야 하는데 이때 표준 라이브러리의 move함수를 이용할 수 있다. move함수는 인수로 전달받은 객체를 r-value 참조를 반환하는 함수이다.
*string 클래스
- C++ 표준 라이브러리에는 string이라는 문자열을 저장하는 클래스를 제공한다.
이 클래스는 basic_string <char>라는 표준 c++라이브러리의 클래스 템플릿으로 선언된 것이다.
string클래스는 문자열에 대한 여러 가지 연산자를 제공하고 있다.
자료형의 변환
서로 다른 클래스의 객체들 사이에서도 형 변환이 일어나도록 할 수 있다.
값을 전달하는 클래스에서 형편을 정의하는 방법은 수신 측 클래스의 이름으로 연산자를 정의하는 것이다.
수신측클래스Name::operator char*() const
{
char *pt = new char[len+1];
return pt;
}
또 수신 측 클래스에서 정의하는 방법이 있는데
값을 제공하는 송신 측 클래스의 객체를 인수로 받는 1 인수 생성자를 정의하는 것입니다.
이와 같은 생성자가 정의되어 있다면 필요한 경우 해당 클래스의 객체로 형 변환됩니다.
다만 기본 자료형이 아닌 타 클래스의 객체로부터 형 변환을 하는 경우는 문제가 발생하는데,
그 이유는 타 클래스의 데이터 멤버에 접근할 수 없기 때문입니다.
이런 경우 타 클래스는 해당 멤버에 접근할 수 있게 상수 멤버 함수를 정의해야 합니다.
연산자 다중 정의의 주의 사항
연산자 다중 정의는 언어의 기능을 확장하는 유용한 기능이지만, 타인이 볼 때는 의미가 명료하게 통하지 않는 경우가 많기 때문이다. 연산자를 다중 정의할 주요 대상은 다음과 같은데.
1. 클래스의 객체 간 대입 연산자 및 이동 대입 연산자의 다중 정의, 특히 동적 할당을 받는 포인터를 포함하는 경우 고려해볼 필요가 있다.
2. 수치형 객체의 경우 산술 연산자의 다중 정의를 하는데 , 연산자의 교환법칙도 함께 고려해야 한다.
3. 두 객체를 비교하기 위한 관계 연산자의 다중정의
4. 스트림 입력 및 출력을 위한 >>, << 연산자
연산자 중에서는 다중정의를 할 수 없는 것도 있다.
1. 멤버 선택 연산자 (.)
2. 멤버에 대한 포인터 연산자(.*)
3. 유효 범위 결정 연산자(::)
4. 조건 연산자(? :)
댓글