ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective C++] 4장(2) - 설계 및 선언
    쾌락없는 책임 (공부)/Effetive C++ 요약본 2022. 3. 29. 15:40
    반응형
    본 카테고리는 프로텍 미디어의 '이펙티브 C++'을 보고 요약하는 카테고리입니다.

    3판을 기준으로 하며 전체 내용이 아닌 간략한 내용만을 요약하고 있습니다.

     

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

     함수 인자로 & 가 좋다고 해도 너무 무분별하게 사용하다가는 있지도 않은 객체의 참조가를 넘기게 되는 경우가 있습니다.

    class Rational{
    public:
    	Rational(int numerator = 0, int denominator = 1);
    	...
    private: 
    	int n, d;
    
    friend
    	const Rational operator * (const Rational& lhs, const Rational& rhs);
    };

     위와 같은 클래스가 있을 때 operator * 함수는 그 결과를 반환하게 되어 있습니다. 

    Rational a(1,2);
    Rational b(3,5);
    
    Rational c = a * b;

      그런데 이런 식으로 사용하면 정말 c 객체가 3/10이 될까요? 생각보다 C++은 거저 만들어주지 않습니다. 이는 객체를 직접 생성해야 한다는 것입니다.

     

     함수 수준에서 새 객체를 만드는건 2가지 방법이 있습니다.

    < 스택에 만드는 방법>

    const Rational& operator * (const Rational& lhs, const Rational& rhs)
    {
    	Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
    	return result;
    }

     이런 방법은 정말 피해야 하며 생성자를 부르기 싫어서 이런 짓을 했다가는 지역 객체에 대한 참조자를 반환하는 큰 문제가 발생하게 됩니다.

     

    <힙에 만드는 방법>

     

    const Rational& operator * (const Rational& lhs, const Rational& rhs)
    {
    	Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
    	return *result;
    }

     new 연산자를 통해서 힙에 객체를 생성합니다. 그래도 여전히 생성자 한번 호출되며 이걸 누가 delete하냐 문제가 생기게 됩니다.

     

     

     결국 다시 보면 위 두가지 방법은 다 문제를 가지고 있게 됩니다. 반환되는 결과는 생성자를 한번 거친다 라는 것입니다. 그리고 이때 사용할 아이디어가 번뜩 떠오르게 됩니다. 바로 객체를 static 객체로 함수 안에 정의해둔 뒤 이것의 참조자를 반환 하는 형식으로 operator *를 만든다! 

    const Rational& operator *(const Rational& lhs, const Rational& rhs)
    {
    	static Rational result;
    	result = ...; // 곱하고 결과를 저장
    	return result;
    }

     순간 좋은 생각이라 생각했지만 실제로는 핵폭탄이 하나 더 생기는 급입니다. 스레드에서 여러 문제가 생기게 되고 만일 Rational로 생성된 a,b,c,d 객체가 있을 때 a==b, c==d 등 == 연산이 전부 true가 될 것입니다!

     

     

     이 수많은 시행착오를 겪고 얻는 깨달음은 참조자 반환 집착증에서 벗어나라는 것입니다. operator * 함수는 아래처럼 내는게 좋습니다.

    inline const Rational operator* (const Rational& lhs, const Rational& rhs)
    {
    	return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
    }

     이러면 생성자의 cost가 있지 않나 하는데 생각보다 적은 비용이고 그 외 짜잘한 오류들도 없어서 간편합니다!

     

     

     

    항목 22 : 데이터 멤버가 선언될 곳은 private 영역임을 명시하자

    public 데이터는 아래와 같은 이유로 안됩니다.

     

    1. 문법적 일관성

    - 데이터 멤버가 public 이 아니라면 항상 멤버 함수로 접근해야 함

    - 전부 함수로 접근하면 일관성 있는 접근법이 가능합니다.

     

    2. 데이터 멤버의 접근성에 대해 훨씬 정교한 제어 가능

    - 값을 읽고 쓰는 함수가 있으면 접근불가, 읽기 전용, 읽기쓰기 전용 등 구현 가능

    - 세밀한 접근 제어 가능

     

    3. 캡슐화

    - 함수를 통해서만 데이터 멤버에 접근할 수 있으면 캡슐화를 지키기 좋다

    - 클래스 불변속성을 지키기 좋음

     

    그리고 protected도 사실상 마찬가지 입니다. public 보다는 보안이 좋다 생각하지만 실제로는 다 비슷합니다! 정 의심된다면 public에 있는 변수를 제거할때와 protected에 있는 변수를 제거할 때를 생각해 봅시다. 둘다 코드가 망가질거에요.

     

     

     

    항목 23 : 멤버 함수보다는 비멤버 비프랜드 함수와 가까워지자

     만약 객체에 있는 함수들을 모아서 한번에 호출하는 함수를 만들고 싶다면 어떻게 해야 할까요? 바로 비멤버 비프랜드 함수로 선언 하는 것입니다.

    void clearBrowser(WebBrowser& wb)
    {
    	wb.clearCache();
    	wb.clearHistory();
    	wb.removeCookies();
    }

     캡슐화 관점에서 봐도 멤버함수가 더 형편이 없으며 비멤버 함수를 사용하면 관련 가능 구현에 있어서 패키징 유연성도 높아지게 됩니다. 비멤버 비프랜드 함수는 클래스 내 private 부분을 더 건드리지도 않고 접근 정도를 해치지도 않기 때문이죠.

     

     그리고 만약 응용도가 높은 함수들의 경우 편의 함수가 많이 생기게 되는데 왠만한 사용자의 경우 몇개만 사용하려고 합니다. 그래서 다른 함수들에 대한 컴파일 의존성을 고민하기 싫은 경우 가 있는 것이죠. 이때는 비슷한 용도의 편의 함수들을 하나의 헤더파일에 몰아서 선언하는 것입니다. 주로 C++의 STL들이 사용하는 방식으로 되어 있는거죠. 클래스 멤버 함수는 이런 식으로 나눌 수 없잖아요? 그리고 이렇게 여러 헤더파일, 하나의 네임스페이스에 넣으면 함수 집합의 확장도 쉬우니깐요.

    반응형

    댓글

Designed by Tistory.