프로그래밍/cpp

연산자 오버로딩 정리 c++

콘파냐 2017. 3. 19. 17:03

앞서 변환생성자와 명시적 변환에 대한 내용을 살펴보았다. 이번에는 연산자 오버로딩의 종류에 대한 내용을 정리할 것인데 앞서 주제와 연관된 내용이 있으므로 주목해서 보도록 하자.


그 전에 Scalar type과 non-Scalar type이 무엇인지 생각해 보면 도움이 된다. vector는 자주 들어봤을 것이다. 보통 스칼라(scalar)는 하나의 속성만 가진 값, 벡터(vector)는 여러 속성을 가진 값으로 이해할 수있다. type의 관점에서 스칼라(Scalar)라고 하면 하나의 값에 대한 정보만 가지고 있는 type을 말한다. C언어에서는 int, char, long, float, double 등 하나의 수로 표시될 수 있는 기본타입을 말한다.

참고 : https://msdn.microsoft.com/en-us/library/94z15h2c.aspx

msdn에 정리되어 있다.

사실 scalar type은 scalar type들 간에 변환이 가능하다. 직접 '=' 대입을 통해서 가능하다. 가끔 '='대입을 해서 변환이 안되고 'error: conversion from scalar to non-scalar type ~~'이런 에러가 날 때가 있다. 변환 생성자에 explicit를 선언해 놓았다면 '=' 통해서 scalar타입 데이터를 대입받아 객체를 생성하려 할 때 위 에러가 난다. 즉, 명시적으로 캐스팅 하라는 뜻이다.


캐스팅에 있어서 scalar 타입과 non-scalar 타입간에 변환을 명시적으로 할 필요성에 대해서는 곧 이해하게 될 것이다.




  • 변환 생성자의 명시적(explicit) 선언의 필요성 

설명이 복잡해 질 것 같아 정리하겠다. 변환 생성자를 명시적으로 사용(캐스팅)하기 위해서 변환생성자에 explicit키워드를 선언해 줘야한다. 그렇지 않다면 실수로라도 온갖 scalar 타입들이 다 대입(=)될 소지가 있기 때문이다.

캐스팅은 반대 방향으로 가능하다. 따라서 역변환 에 대한 것도 고려해 줘야한다. 이를 위해서 캐스팅 연산자를 오버로딩 했었다. 


  • 캐스팅 연산자 오버로딩은 반환 타입이 없지만 값을 반환하는 구현을 해야한다.

캐스팅 연산자 오버로딩 시에는 선언에 반환 타입은 선언하지 않지만 내부적으로 변환 된 값은 반환해 줘야한다. 반환 타입을 선언하지 않는 이유는 캐스팅 연산자 오버로딩을 할 때 변환할 타입이름이 연산자 이름으로 이미 언급되기 때문이다. 반환값에 중복해서 쓸 필요는 없지 않은가?


  • 역변환 연산 역시 scalar 타입들 간에 암묵적인 변환을 막기 위해 명시적(explicit)변환 선언 필요

그리고 중요한 것은 변환 생성자와 마찬가지로 explicit 선언을 해 주는 것이 좋다. int형 캐스팅으로 연산자 오버로딩을 하더라도 scalar 타입간에는 변환이 가능하기 때문에 float타입이나 long타입 변수에 '='연산을 통해 대입 될 수도 있다.(암묵적인 변환) 따라서 역시 명확한 캐스팅 표현으로 실수를 줄여주기 위해 explict키워드를 붙여준다.


위 예에서 Myfloat 타입 객체가 int형 i에 암묵적으로 대입되었다. 만약 explicit선언이 없다면 결과는 소수점 이하 값들이 없어지는 사태가.. 이런 예에서 알 수 있듯이 명시적 형변환은 중요하다.

좀 찜찜하게 정리를 했지만 모자란 2%는 나중에 보충하기로 하고 연산자 오버로딩의 종류에 대한 정리를 하려한다.



  • 연산자 오버로딩 형식 정리

연산의 종류는 매우 다양하다. 산술, 비트, 비교, <<, >>, 논리연산, 인덱싱 [ ], 변환 연산(참고:변환생성자),  메모리 할당 연산 new, 역참조 연산 *, 멤버 참조 연산자 -> 등등.. 그 와중에 오버로딩이 안되는 연산자는 &(주소연산), .(접근연산) 등이 있다.

이 많은 연산자들을 오버로딩할 경우가 자주 필요한 것이 아니므로 레퍼런스를 참조하여 사용하면 되겠다. 그래도 자주 사용하거나 특수한 경우는 정리해 놓고 넘어가는 것이 좋다.


위에서 설명한 변환 연산자도 다른 연산자들과 달리 연산자 선언 시 반환값을 선언하지 않는다는 특수성과 explicit 선언을 권장한다는 것 역시 그렇다.

어떤 연산자의 경우는 friend의 선언이 필요한 경우도 있는데 피 연산자의 순서가 반대로 될 때에 대한 연산을 정의하기 위해 friend로 선언해서 전역 연산자 오버로딩을 할 필요가 있기 때문이다. 


friend const MyType operator+(OrtherType& A, MyType& B);  // -, *, /, % 연산도 마찬가지



이항 연산자 오버로딩의 종류


우선 일반적으로 friend로 선언할 필요가 있는 연산자들을 먼저 보자. 형식은 비슷하므로 생략한다.

operator<<

operator>>

operator&

operator|

operator^


operator&&

operator||


operator<

operator>

operator<=

operator>=

operator==

operator!=


friend ostream& operator<<(ostream&, const MyType& B); //출력 스트림 연산자

friend ostream& operator>>(ostream&, const MyType& B); //입력 스트립 연산자


위 연산자들은 이항연산자로 다른 타입과의 연산에서 피연산자의 순서를 고려해야 한다. 앞서 말한 것 처럼 friend 선언으로 전역 함수 오버로딩으로 해결할 수 있다.



단항 연산자 오버로딩의 종류


그 외에 연산자들은 단항연산자들로 다양한 종류가 있다. 이런 연산자들은 연산자 오버로딩 시 friend로 선언할 필요도 없고 그러지도 말자.

const MyType operator+() const;    //단항 연산자 +

const MyType operator~() const;    


MyType& operator++();   //전위 증가 연산자

Mytype operator++(int); //후위 증가 연산자.


더 많은 연산자는 레퍼런스를 참조하도록 하자. 여기서는 전위 연산자와 후위 연산자 그리고 캐스팅 연산자정도면 주의해서 살펴보면 될 것이다. 이에 대한 것은 따로 정리하도록 하겠다.

반응형