Const C ++ DRY 전략


14

사소한 C ++ const 관련 중복을 피하기 위해 const_cast는 작동하지만 non-const를 반환하는 개인 const 함수는 그렇지 않은 경우가 있습니까?

Scott Meyers의 Effective C ++ 항목 3에서 정적 캐스트와 결합 된 const_cast는 중복 코드를 피하는 효과적이고 안전한 방법 일 수 있다고 제안합니다.

const void* Bar::bar(int i) const
{
  ...
  return variableResultingFromNonTrivialDotDotDotCode;
}
void* Bar::bar(int i)
{
  return const_cast<void*>(static_cast<const Bar*>(this)->bar(i));
}

Meyers는 const 함수를 non-const 함수로 호출하는 것이 위험하다고 설명합니다.

아래 코드는 다음과 같은 반례입니다.

  • Meyers의 제안과 달리, 때로는 정적 캐스트와 결합 된 const_cast가 위험합니다
  • 때로는 const 함수가 비 const를 호출하는 것이 덜 위험합니다.
  • 때로는 const_cast를 사용하는 두 가지 방법으로 잠재적으로 유용한 컴파일러 오류 숨기기
  • const_cast를 피하고 추가 const 개인 멤버가 const가 아닌 것을 반환하는 것은 또 다른 옵션입니다.

코드 복제를 피하는 const_cast 전략 중 하나가 모범 사례로 간주됩니까? 대신 개인 방법 전략을 원하십니까? const_cast는 작동하지만 개인 메소드는 작동하지 않는 경우가 있습니까? 다른 옵션 (복제 외에)이 있습니까?

const_cast 전략에 대한 나의 관심은 코드를 작성할 때 코드가 정확하더라도 나중에 유지 관리 중에 코드가 잘못 될 수 있고 const_cast가 유용한 컴파일러 오류를 숨길 수 있다는 것입니다. 일반적인 개인 기능이 일반적으로 더 안전한 것 같습니다.

class Foo
{
  public:
    Foo(const LongLived& constLongLived, LongLived& mutableLongLived)
    : mConstLongLived(constLongLived), mMutableLongLived(mutableLongLived)
    {}

    // case A: we shouldn't ever be allowed to return a non-const reference to something we only have a const reference to

    // const_cast prevents a useful compiler error
    const LongLived& GetA1() const { return mConstLongLived; }
    LongLived& GetA1()
    {
      return const_cast<LongLived&>( static_cast<const Foo*>(this)->GetA1() );
    }

    /* gives useful compiler error
    LongLived& GetA2()
    {
      return mConstLongLived; // error: invalid initialization of reference of type 'LongLived&' from expression of type 'const LongLived'
    }
    const LongLived& GetA2() const { return const_cast<Foo*>(this)->GetA2(); }
    */

    // case B: imagine we are using the convention that const means thread-safe, and we would prefer to re-calculate than lock the cache, then GetB0 might be correct:

    int GetB0(int i) { return mCache.Nth(i); }
    int GetB0(int i) const { return Fibonachi().Nth(i); }

    /* gives useful compiler error
    int GetB1(int i) const { return mCache.Nth(i); } // error: passing 'const Fibonachi' as 'this' argument of 'int Fibonachi::Nth(int)' discards qualifiers
    int GetB1(int i)
    {
      return static_cast<const Foo*>(this)->GetB1(i);
    }*/

    // const_cast prevents a useful compiler error
    int GetB2(int i) { return mCache.Nth(i); }
    int GetB2(int i) const { return const_cast<Foo*>(this)->GetB2(i); }

    // case C: calling a private const member that returns non-const seems like generally the way to go

    LongLived& GetC1() { return GetC1Private(); }
    const LongLived& GetC1() const { return GetC1Private(); }

  private:
    LongLived& GetC1Private() const { /* pretend a bunch of lines of code instead of just returning a single variable*/ return mMutableLongLived; }

    const LongLived& mConstLongLived;
    LongLived& mMutableLongLived;
    Fibonachi mCache;
};

class Fibonachi
{ 
    public:
      Fibonachi()
      {
        mCache.push_back(0);
        mCache.push_back(1);
      }

      int Nth(int n) 
      {
        for (int i=mCache.size(); i <= n; ++i)
        {
            mCache.push_back(mCache[i-1] + mCache[i-2]);
        }
        return mCache[n];
      }

      int Nth(int n) const
      {
          return n < mCache.size() ? mCache[n] : -1;
      }
    private:
      std::vector<int> mCache;
};

class LongLived {};

멤버를 반환하는 getter는 다른 버전의 자신을 캐스팅하고 호출하는 것보다 짧습니다. 중복 제거의 이득이 캐스팅의 위험을 능가하는보다 복잡한 기능을위한 트릭입니다.
Sebastian Redl

@SebastianRedl 나는 회원을 반환하면 복제가 더 나을 것이라는 데 동의합니다. 예를 들어 mConstLongLived를 반환하는 대신 mConstLongLived에 대한 함수를 호출하여 const 참조를 반환하는 함수를 호출 할 수 있습니다.이 함수는 소유하지 않고 액세스 할 수있는 const 참조를 반환하는 다른 함수를 호출하는 데 사용됩니다. const 버전. const_cast가 const가 아닌 접근 권한이없는 무언가에서 const를 제거 할 수 있다는 점이 분명하기를 바랍니다.
JDiMatteo

4
이 모든 것은 간단한 예제로 어리석은 것처럼 보이지만 const 관련 중복은 실제 코드로 나타나고 const 컴파일러 오류는 실제로 유용합니다 (종종 바보 같은 실수를 잡기 위해 유용합니다). 오류가 발생하기 쉬운 캐스트 쌍입니다. 비 const를 반환하는 개인 const 멤버는 이중 캐스트보다 분명히 뛰어나고 누락 된 것이 있는지 알고 싶습니다.
JDiMatteo

답변:


8

반환 된 ptr / reference가 const인지에 따라 다른 const와 non-const 멤버 함수를 구현할 때 가장 좋은 DRY 전략은 다음과 같습니다.

  1. 접근 자를 작성하는 경우 접근자가 정말로 필요한지 고려하십시오. cmaster 's answerhttp://c2.com/cgi/wiki?AccessorsAreEvil을 참조하십시오 .
  2. 코드가 사소한 경우 코드를 복제하십시오 (예 : 멤버를 반환)
  3. const 관련 중복을 피하기 위해 const_cast를 사용하지 마십시오
  4. 사소한 중복을 피하려면 const 및 non-const 공용 함수가 호출하는 non-const를 리턴하는 개인 const 함수를 사용하십시오.

예 :

public:
  LongLived& GetC1() { return GetC1Private(); }
  const LongLived& GetC1() const { return GetC1Private(); }
private:
  LongLived& GetC1Private() const { /* non-trivial DRY logic here */ }

이것을 const가 아닌 const를 리턴 하는 private const 함수 라고하자 .

이것은 컴파일러가 잠재적으로 유용한 검사를 수행하고 const 관련 오류 메시지를보고 할 수 있도록하는 동시에 중복을 피하기위한 최선의 전략입니다.


당신의 주장은 다소 설득력이있다,하지만 난 오히려 당신이 뭔가에 const가 아닌 참조를 얻을 수있는 방법 의아해하고 const선언의 기준이 무엇인가하지 않는 한 (예 mutable, 또는 당신은을 사용하지 않는 const_cast하지만 두 경우 모두 시작 할 probkem 없다 ). 또한 나는 (재미는 밤은 ....이 패턴을 호출하는 농담을 의도 한 경우)은 "const가 아닌 패턴을 반환 개인 CONST 기능"에 아무것도 찾을 수 couldnt한다
463,035,818 idclev

1
다음은 질문의 코드를 기반으로 한 컴파일 예제입니다 : ideone.com/wBE1GB . 미안하지만, 나는 그것을 농담으로 의미하지는 않았지만 여기에 이름을 지어 주었을 것입니다 (아마도 이름이 가치가 있음), 더 명확하게하기 위해 대답의 문구를 업데이트했습니다. 이 글을 쓴 지 몇 년이 지났으며 생성자에서 참조를 전달하는 예제가 적합하다고 생각한 이유를 기억하지 못합니다.
JDiMatteo

예를 들어 주셔서 감사합니다. 지금 시간이 없지만 확실히 다시 올 것입니다. Fwiw는 동일한 접근 방식을 제시하는 답변이며 유사한 의견이 지적되었습니다. stackoverflow.com/a/124209/4117728
idclev 463035818

1

그렇습니다. 맞습니다. const-correctness를 시도하는 많은 C ++ 프로그램은 DRY 원칙을 심각하게 위반하고 있으며, non-const를 반환하는 개인 구성원조차도 편의를 위해 너무 복잡합니다.

그러나 한 가지 관찰은 그리워합니다. const-correctness로 인한 코드 복제는 데이터 멤버에 다른 코드 액세스를 제공하는 경우에만 문제가됩니다. 이것은 그 자체로 캡슐화를 위반하는 것입니다. 일반적으로 이러한 종류의 코드 복제는 대부분 간단한 접근 자에서 발생합니다 (결국 기존 멤버에 대한 액세스를 전달하는 경우 반환 값은 일반적으로 계산 결과가 아님).

내 경험은 좋은 추상화에는 접근자가 포함되지 않는 것입니다. 결과적으로 데이터 멤버에 대한 액세스를 제공하는 대신 실제로 무언가를 수행하는 멤버 함수를 정의하여이 문제를 피할 수 있습니다. 데이터 대신 행동을 모델링하려고합니다. 이것의 주요 목적은 객체를 데이터 컨테이너로 사용하는 대신 실제로 클래스와 개별 멤버 함수 모두에서 추상화를 얻는 것입니다. 그러나이 스타일은 대부분의 코드에서 일반적으로 사용되는 수많은 const / const 반복적 인 한 줄 접근자를 피하는 데 매우 성공적입니다.


접근자가 좋은지 아닌지 토론하는 것 같습니다 (예 : c2.com/cgi/wiki?AccessorsAreEvil 의 토론 참조) . 실제로 접근 자에 대해 어떻게 생각하든 큰 코드 기반은 종종이를 사용하며, 사용하는 경우 DRY 원칙을 따르는 것이 좋습니다. 그래서 질문은 당신이 묻지 말아야 할 것보다 더 많은 대답을 할 가치가 있다고 생각합니다.
JDiMatteo

1
확실히 물어 볼만한 질문입니다 :-) 그리고 때로는 접근자가 필요하다는 것을 부인하지 않을 것입니다. 접근자를 기반으로하지 않는 프로그래밍 스타일이 문제를 크게 줄인다는 것입니다. 그것은 문제를 완전히 해결하지는 못하지만 적어도 나에게는 충분합니다.
cmaster-monica reinstate
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.