Const C ++ DRY戦略


14

非自明なC ++ const関連の重複を避けるために、const_castは機能するが、非constを返すプライベートconst関数は機能しない場合がありますか?

Scott MeyersのEffective C ++ item 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関数にnon-const関数を呼び出すことは危険であると説明しています。

以下のコードは反例を示しています。

  • マイヤーズの提案に反して、静的キャストと組み合わせたconst_castは危険な場合があります
  • 時々const関数がnon-constを呼び出すことはそれほど危険ではありません
  • const_castを使用して両方の方法で潜在的に有用なコンパイラエラーを隠すことがあります。
  • const_castを避け、const以外のメンバーを返す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 {};

メンバーを返すだけのゲッターは、それ自体の他のバージョンをキャストして呼び出すゲッターよりも短くなります。このトリックは、重複排除の利益がキャストのリスクを上回る、より複雑な機能を対象としています。
セバスチャンレッド

@SebastianRedlメンバーを返すだけで複製が良くなることに同意する。それがより複雑であることを想像してください。たとえば、mConstLongLivedを返す代わりに、const参照を返すmConstLongLivedの関数を呼び出すことができます。この関数は、所有せずにアクセスするconst参照を返す別の関数を呼び出すために使用されますconstバージョン。const_castが、constを非constアクセスを持たないものからconstを削除できるという点が明確であることを願っています。
JDiMatteo

4
これはすべて単純な例ではばかげているように見えますが、const関連の重複は実際のコードで発生し、constコンパイラエラーは実際に役立ちます(多くの場合、愚かなミスをキャッチするため)。そして一見エラーが発生しやすいキャストのペア。非constを返すプライベートconstメンバーは、ダブルキャストよりも明らかに優れているように見えます。
JDiMatteo

回答:


8

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

  1. アクセサーを作成する場合は、アクセサーが本当に必要かどうかを検討してください。cmasterの回答http://c2.com/cgi/wiki?AccessorsAreEvilを参照してください。
  2. 簡単な場合はコードを複製します(たとえば、メンバーを返すだけ)
  3. const関連の重複を避けるためにconst_castを使用しないでください
  4. 重要な重複を避けるために、constおよびnon-constパブリック関数の両方が呼び出す非constを返すプライベートconst関数を使用します

例えば

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

これを非constパターンを返すプライベートconst関数と呼びましょう。

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


あなたの議論はかなり説得力がありますが、constインスタンスから何かへの非const参照を取得する方法はかなり困惑しています(参照が宣言されたものへの参照でmutableない限り、またはconst_cast両方を使用しない限り、最初から問題はありません) )。また、私は「非constパターンを返す民間のconst機能」に何かを見つけるcouldntの(それがパターンにそれを呼び出すための冗談であることを意図していた場合....それは面白いイマイチ;)
idclev 463035818

1
:ここでは、問題のコードオフに基づいてコンパイルする例ですideone.com/wBE1GB。申し訳ありませんが、冗談としてではありませんでしたが、ここに名前を付けることを意味しました(まれに、名前に値する場合があります)。これを書いてから数年が経ちましたが、なぜコンストラクターで参照を渡す例が関連性があると思ったのか覚えていません。
JDiMatteo

例のおかげで、今は時間がありませんが、間違いなく戻ってきます。FWIWは、ここでの答えであることを提示同様の問題が指摘されている同様のアプローチとコメントで:stackoverflow.com/a/124209/4117728
idclev 463035818

1

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

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

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


アクセサーが良いかどうかは議論の余地があるようです。例えば、c2.com/cgi/wiki?AccessorsAreEvilの議論を参照してください。実際には、アクセサについての考えに関係なく、大規模なコードベースはしばしばアクセサを使用します。アクセサを使用する場合は、DRYの原則に従うことをお勧めします。ですから、質問はあなたが尋ねるべきではないというよりも、答えに値すると思います。
JDiMatteo

1
それは間違いなく質問する価値のある質問です:-)そして、時々アクセサーが必要であることも否定しません。アクセッサに基づいていないプログラミングスタイルは、問題を大幅に減らすと言っているだけです。問題を完全に解決するわけではありませんが、少なくとも私には十分です。
cmaster-モニカの復元15年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.