이 문제는 C++ 이디엄(idiom)으로 자주 추천 받고 있지만, 잘못 사용되면 심각한 문제가 발생할 수 있다는 것을 고려해야 한다.

이디엄을 보도록 하자.

 

문제

  1. 이 코드는 어떤 일을 하기 위해서 작성 되었을까? 이 코드의 잘못된 점을 찾아 고쳐라
  2. 잘못된 점을 고치더라도, 이 이디엄이 안전한가? 안전하지 않다면 어떻게 해야만 프로그래머의 의도되로 될 수 있을까?

 

부연 설명

이 예제는 개체의 생성 법칙에 대하여 증명하기 위한 용도로 사용 될 뿐, 좋은 연습에 관련된 예제라고 추천하지 않습니다.

 

 

분석

(1). 어떤 일을 하며, 잘못된 점은 무엇인가?

  1. 이 코드는 복사 생성자를 통하여, 복사 할당자를 구현하는데 그 목적을 두고 있다. 왜냐하면 복사 생성자나 복사 할당자는 같은 일을 수행하기 때문이다. 굳이 같은 일을 하는데, 따로 구현할 필요가 없을 뿐더러, 복사 방식이 달라질 경우 두 군데서 수정을 해야 하는 경우가 생긴다.
  2. 잘못된 점은, 개체가 분활 될 수 있으며, 예외에 안전하지 않고, 일반적인 개체 활동주기를 변화시키고, 파생 클래스를 엉망으로 만들 수 있으며, 자기 복사 검사에서 예외가 날 수 있다.

그렇다면, 각 문제점을 수정 하도록 해보자.

[1] 개체가 분활 될 수 있다.

T::~T()가 가상 소멸자라면,  T의 Derived 클래스의 소멸자가 호출 되어 Derived 클래스가 파괴되고 T::~T()가 파괴 된 후에 T::T() 만 호출 하여, 생성 되어 지므로, 문제가 될 수 있다. 물론 이런 경우에 다시 값을 밀어 넣어 주면 된다. 하지만 이것은 상속받은 사람에게 고된 작업을 떠 넘기는 꼴이 되고야 만다.

해결법으론 this->~T() 호출을 하지 말고, this->T::~T() 호출을 함으로써 그 활동 범위에 제한을 준다. 이렇게 되면 T만 교체 된다. (이 방법의 함정은 뒤에가서 따진다.) 여기서 좀더 들어 간다면 T::operator=( other ); 하기전에 U객체 먼저 할당하고, T 객체 부분만 재생성하면 이게 더 좋을 것이다.

하지만 T 객체를 생속 하는 모든 파생 객체는 이 구조로 따라 가야 한다. 왜냐하면, 컴파일러에 의해서 생성되는 복사 할당자와 복사 생성자는 절대로 이렇게 만들어 주지 않기 때문이다.

가이드 라인

언어의 잘 모르는 부분은 피해 가는게 제일 좋다(.. 귀가 따겁게 들린다 )

 

[2]. 이 해결책이 과연 안전할까?

① 우선 예외에 안전하지 못하다.

"판 감마 비젠"이 하던 방식데로 다 작업 될때까지 기다렸다가 낼름 swap 으로 받아 먹는 방식이 아니라, 소멸자 호출 후에, new (this) ... 가 정상적으로 호출 되지 않으면, 울어야 할 판이기 때문이다.

② 일반적인 개체 활동주기를 변화시킨다.

일반적인 개체의 활동주기란, scope 를 벗어 날 경우와 delete 할 경우이다. 하지만 명시적으로 소멸자를 호출하게 되면 개체의 활동 주기가 변화하므로, 생성자와 소멸자에 어떠한 의미를 부여했다면, 그 의미가 제대로 작동하지 않게 된다.

예를 들자면, 뮤텍스를 생성자에서 할당을 해 두고, 소멸자에서 풀어 주는 구조로 간다고 쳐보자. 일반적인 개체 활동 주기로 코딩을 하다가, operator= 을 하게 되었다. 뮤텍스는 풀리게 되었고 그 찰나에, 다른 쓰레드에서 들어와 작업을 하게 된다.

결국 정상적인 흐름과는 다르게 움직이게 되는 것이다.

근본적인 문제는 개체 활동 주기를 임의로 바꾼다는 것 자체는 goto 문을 사용하여 코딩하는 것과 똑같다.

③ 여전히 파생 클래스를 엉망으로 만들 수 있다.

T의 활동 범위는 U의 활동 범위 안에 포함되어 있다. 그것이 클래스 이므로 당연하다. 하지만 예제 코드처럼 운용하게 된다면, 이 클래스에 대해 신용을 할 수 없게 된다.

어찌 보면 파생 클래스 생성자가 자신의 코드를 신용하고 움직인다 하더라도 예상할수 없는 결과가 나올 경우, 문제를 어디서 찾아야 할지 모르는 결과를 초래 하게 된다.

더군다나 여러명이 작업하고 있는 환경이라면, 그야 말로 최악의 시나리오로 진행 하게 될 것이다.

④ this != other 가 잘못 될 수도 있다.

우선 이 부분을 집고 넘어 가기 전에 최적화를 위해선 개인적으로 this != other 이 좋다. 하지만 예외 안전성에선 후퇴하게 된다. 왜냐하면 != 비교에 대해서 항목 38에 설명 되어 있다.

결국 예외를 일으키지 않는 연산만을 이용하여, 자기복사에 대한 처리를 해야 하는데, 저 구조로 간다면, 예외를 안 뱉어 낼수가없기 때문이다.

결론적으로 원래의 이디엄은 함정으로 가득 차 있어서 항상 문제가 생기기 때문에, 하지 않는게 오히려 더 편하다.

총평

해당 이디엄은 복사 할당자를 복사 생성자로 구현 할수 있는 부분을 표현한 예의 한 종류이지, 이것이 좋다는 것이 아니다. 일반적이지 않은것을 사용했을 때 얻게되는 좋은 점도 있지만, 오히려 독이 되는 경우가 많다는 것을 배웠다.

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 라이프코리아트위터 공유하기
  • shared
  • 카카오스토리 공유하기