방프리

17.08.07. Effective C++ 1. C++에 왔으면 C++의 법을 따릅시다. (항목21) 본문

C++/Effective C++

17.08.07. Effective C++ 1. C++에 왔으면 C++의 법을 따릅시다. (항목21)

방프리 2020. 1. 13. 04:17

항목 21 : 함수에서 객체를 반환해야할 경우에 참조자를 반환하려고 들지 말자

 

일반적인 데이터 타입에서 사용되는 연산자(operator)는 미리 정의되어 있지만, 사용자가 정의한 클래스의 경우

연산자가  정해져 있지 않습니다. 이 덕분에 직접 operator 키워드를 통해 연산자를 재정의해서 사용하지요.

이 때 프로그래머들이 쉽게 실수할 수 있는 간단한 예를 살펴보며 하나씩 문제점을 짚어보겠습니다.

 

(공통으로 사용되는 변수) 

Rational a(1, 2); // a = 1/2

Rational b(3, 5); // b = 3/5

Rational c = a * b; // c는 3/10이어야 합니다.

 

 

예제1)

const Rational& operator* (const Rational& lhs, const Rational& rhs)

{

Rational result( lhs.n * rhs.n, lhs.d * rhs.d);

return result;

 

예제 1의 코드의 문제점은 바로 지역객체를 반환하는 것입니다.

코드를 살펴보면 result라는 지역변수를 참조자로 반환하고 있습니다.

C++의 기본을 아시는 분이라면 지역객체는 함수를 빠져나오면 소멸된다는 것을 알고 계실 것입니다.

소멸된 객체의 참조자를 반환한다? 이미 메모리가 해제되어 있는 객체를 반환해봐야 아무 소용이 없다는 것을

아실 겁니다.

 

예제 2)

const Rational& operator* (const Rational& lhs, const Rational& rhs)

{

Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);

return *result;

}

 

예제 2의 코드는 언뜻보면 제대로 만들어진 것 같습니다. 객체에 메모리를 할당한다는 것은 같지만

Stack에 쌓인 메모리가 아닌 Heap에 쌓은 메모리를 반환함으로써 유지를 시켜주는 것이죠.

하지만 이 코드는 다중연산에 매우 취약합니다.

Rational w,x,y,z;

w = x * y * z;

다음과 같은 코드가 있다면 컴파일러는 자동으로 w = (x * (y * z)); 로 변환할 것입니다.

이렇게 되면 연산하는 양만큼 메모리를 할당하지만 해제는 하지 않습니다.

즉, 메모리 누수의 방지가 없다는 것입니다.

그럼 정적 객체로 해보는 것은 어떨까요?

예제 3)

const Rational& operator*(const Rational& lhs, const Rational& rhs)

{

static Rational result;

 

result = ....;

 

return result;

}

이렇게 할 경우엔 쓰레드의 안정성 문제가 일어날 뿐만 아니라 여러 가지에서 문제가 생길 수도 있습니다.

단적인 예로 배열의 경우에 큰 문제가 생길 수도 있지요.

가장 좋은 방법은 '새로운 객체를 반환하게 만드는 것'입니다. 다음 예제 코드를 보겠습니다.

예제 4)

inline const Rational operator*( const Rational& lhs, const Rational& rhs)

{

return Rational(lhs.n * rhs.n, lhs.d * rhs.d);

}

바로 객체를 생성함과 동시에 반환하는 것이지요. 예제 1, 2, 3번보다 작은 연산으로 최상의 결과값을 얻을 수 있게

되었습니다.

 

이것만은 잊지 말자!

* 지역 스택 객체에 대한 포인터나 참조자를 반환하는 일, 혹은 힙에 할당된 객체에 대한 참조자를 반환하는 일, 또는 지역 정적 객체에 대한 포인터나 참조자를 반환하는 일은 그런 객체가 두 개 이상 필요해질 가능성이 있다면 절대로 하지 마세요(항목 4를 보시면 지역 정적 객체에 대한 참조자를 반환하도록 설계된 올바른 코드 예제를 찾을 수 있습니다. 최소한 단일 스레드 환경에서는 통합니다.)

Comments