web-dev-qa-db-ja.com

Const C ++ DRY戦略

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));
}

マイヤーズはさらに、const関数が非const関数を呼び出すことは危険であることを説明します。

以下のコードは、反例です。

  • マイヤーズの提案に反して、静的キャストと組み合わせたconst_castは危険な場合があります
  • const関数がnon-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 {};
14
JDiMatteo

返されるptr /参照がconstであるかどうかだけが異なるconstおよびnon-constメンバー関数を実装する場合、最適なDRY戦略は次のとおりです。

  1. アクセサーを作成する場合は、本当にアクセサーが必要かどうかを検討してください。 cmaster's answer および http://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 */ }

これをprivate const関数と呼びましょう。非constパターンを返します。

これは、コンパイラが潜在的に有用なチェックを実行し、const関連のエラーメッセージを報告できるようにしながら、重複を簡単に回避するための最良の戦略です。

8
JDiMatteo

はい、そうです。const-correctnessを試みる多くのC++プログラムは、DRYの原則にまったく違反しています。また、非constを返すプライベートメンバーでさえ、複雑すぎて快適ではありません。

ただし、1つの見落としがあります。const-correctnessによるコードの重複は、データメンバーに他のコードアクセスを許可している場合にのみ問題になります。これ自体はカプセル化に違反しています。一般に、この種のコードの重複は、ほとんどの場合単純なアクセサーで発生します(結局、既存のメンバーにアクセス権を渡しており、戻り値は一般に計算の結果ではありません)。

私の経験では、優れた抽象化にはアクセサが含まれる傾向はありません。したがって、データメンバーへのアクセスを提供するだけでなく、実際に何かを実行するメンバー関数を定義することで、この問題を大幅に回避しています。データではなく動作をモデル化しようとします。これの主な目的は、オブジェクトをデータコンテナーとして使用するだけでなく、クラスと個々のメンバー関数の両方から実際にいくつかの抽象化を実現することです。しかし、このスタイルは、ほとんどのコードで一般的である、大量のconst/non-constの反復的な1行アクセサーを回避するのにも非常に成功しています。