web-dev-qa-db-ja.com

複製、constおよび非const、getterに対するエレガントなソリューション?

あなたが持っているとき、あなたはそれを嫌ってはいけません

class Foobar {
public:
    Something& getSomething(int index) {
        // big, non-trivial chunk of code...
        return something;
    }

    const Something& getSomething(int index) const {
        // big, non-trivial chunk of code...
        return something;
    }
}

constバージョン(コンパイラエラー)から非constバージョンを呼び出すことはできないため、このメソッドのいずれかを他のメソッドと共に実装することはできません。非constバージョンからconstバージョンを呼び出すにはキャストが必要です。

これに対する本当のエレガントな解決策はありますか?ない場合、最も近いものは何ですか?

111
Michael

効果的なC++の本の1つから、それを行う方法は、constを他の関数からキャストすることで非constバージョンを実装することだと思います。

特にきれいではありませんが、安全です。それを呼び出すメンバー関数は非constであるため、オブジェクト自体は非constであり、constのキャストは許可されています。

class Foo
{
public:
    const int& get() const
    {
        //non-trivial work
        return foo;
    }

    int& get()
    {
        return const_cast<int&>(const_cast<const Foo*>(this)->get());
    }
};
99
CAdaker

どうですか:

template<typename IN, typename OUT>
OUT BigChunk(IN self, int index) {
    // big, non-trivial chunk of code...
    return something;
}

struct FooBar {
    Something &getSomething(int index) {
        return BigChunk<FooBar*, Something&>(this,index);
    }

    const Something &getSomething(int index) const {
        return BigChunk<const FooBar*, const Something&>(this,index);
    }
};

明らかにオブジェクトコードの重複はありますが、ソースコードの重複はありません。 const_castアプローチとは異なり、コンパイラはメソッドの両方のバージョンのconst-correctnessをチェックします。

BigChunkの2つの興味深いインスタンス化をクラスのフレンドとして宣言する必要があるでしょう。フレンド機能はフレンドの近くに隠されているため、これはフレンドの有効な使用方法であり、制約のないカップリングのリスクはありません(ooh-er!)。しかし、今のところそうするための構文は試みません。気軽に追加してください。

BigChunkが自己を尊重する必要がある可能性があります。その場合、上記の定義の順序はうまく機能せず、それを整理するにはいくつかの前方宣言が必要になります。

また、ヘッダーでBigChunkを見つけて、それが道徳的にプライベートであるにもかかわらずインスタンス化して呼び出すことを決定するために、いくつかのむちゃくちゃを避けるために、FooBarのcppファイルに全体を移動できます。匿名の名前空間。内部リンケージあり。そして、「ヒョウに注意してください」というサイン。

27
Steve Jessop

Constを非constにキャストします(2番目のオプション)。

7

なぜ共通のコードを独立したプライベート関数に引き出してから、他の2つの関数にそれを呼び出さないのですか?

5
Jay

コードをリファクタリングして、ゲッターを排除してください。他の非常に少数のものがSomethingを必要とする場合、フレンド関数またはクラスを使用します。

一般的に、データが世界に公開されるため、ゲッターとセッターはカプセル化を破ります。 friendを使用すると、選択した少数のユーザーにのみデータが公開されるため、カプセル化が向上します。

もちろん、これは常に可能であるとは限らないので、ゲッターにこだわることがあります。少なくとも、「重要なコードのチャンク」のほとんどまたはすべては、両方のゲッターによって呼び出される1つ以上のプライベート関数に含まれている必要があります。

4
markh44

オブジェクトへのconst参照は理にかなっています(そのオブジェクトへの読み取り専用アクセスに制限をかけています)が、const以外の参照を許可する必要がある場合は、メンバーを公開します。

これはScott Meyers(Efficient C++)であると思います。

3
swongu

「定数」の概念には理由があります。私にとって、それはプログラムのさらなる指示が書かれていることに基づいて非常に重要な契約を確立します。しかし、次の行で何かを行うことができます:-

  1. メンバーを「可変」にする
  2. 「ゲッター」をconstにする
  3. 非const参照を返します

これにより、非const使用法(危険)とともにゲッターを使用しているconst機能を維持する必要がある場合、LHSでconst参照を使用できます。しかし、クラスの不変条件を維持する責任はプログラマにあります。

前にSOで述べたように、最初に定義されたconstオブジェクトのconstnessをキャストし、それを使用することはUBです。したがって、キャストを使用しません。 constnessをキャストすることはあまり良くありません。

私がいくつかのチームで使用しているのを見た別のコーディングガイドラインは次のとおりです。

  • メンバー変数をクラス外で変更する必要がある場合は、非constメンバー関数を介して常にポインターを返します。
  • メンバー関数は非const参照を返すことはできません。 constメンバー関数からはconst参照のみが許可されます。

これにより、コードベース全体の一貫性が確保され、呼び出し元はどの呼び出しがメンバー変数を変更できるかを明確に確認できます。

2
Abhay