10

const-correctnessを目指して努力していると、私はしばしばこのようなコードを書いていることに気づきます。

class Bar;

class Foo {
public:
  const Bar* bar() const { /* code that gets a Bar somewhere */ }

  Bar* bar() {
    return const_cast< Bar* >(
      static_cast< const Foo* >(this)->bar());
  }
};

のような多くのメソッドの場合bar()。constメソッドを手動で呼び出すこれらのnon-constメソッドを作成するのは面倒です。その上、私は自分自身を繰り返しているように感じます–それは私を気分が悪くさせます。

このタスクを軽減するために何ができますか?(マクロおよびコードジェネレーターは許可されていません。)

編集:litbのソリューションに加えて、私も自分のソリューションが好きです。:)

4

11 に答える 11

7

別の方法は、(CRTPを使用して)関数を呼び出し、それから継承するテンプレートを作成することです。

template<typename D>
struct const_forward {
protected:
  // forbid deletion through a base-class ptr
  ~const_forward() { }

  template<typename R, R const*(D::*pf)()const>
  R *use_const() {
    return const_cast<R *>( (static_cast<D const*>(this)->*pf)() );
  }

  template<typename R, R const&(D::*pf)()const>
  R &use_const() {
    return const_cast<R &>( (static_cast<D const*>(this)->*pf)() );
  }
};

class Bar;

class Foo : public const_forward<Foo> {
public:
  const Bar* bar() const { /* code that gets a Bar somewhere */ }
  Bar* bar() { return use_const<Bar, &Foo::bar>(); }
};

呼び出しのパフォーマンスが低下しないことに注意してください。メンバーポインターはテンプレートパラメーターとして渡されるため、呼び出しは通常どおりインライン化できます。

于 2009-08-26T21:39:39.933 に答える
4

次のトリックを使用します。


class Bar;
class Foo {
public:  
  Bar* bar() { 
    // non-const case
    /* code that does something */ 
  }  
  const Bar* bar() const {    
      return This().bar();  // use non-const case
   }

private:
  //trick: const method returns non-const reference
  Foo & This() const { return const_cast<Foo &>(*this); } 
};

Thisconst/non-const関数には一意の関数を使用できることに注意してください。

static_castを使用しない代替ソリューション(ただし、最初のソリューションをお勧めします):

class Bar;
class Foo {
public:  
  const Bar* bar() const { /* code that does something */ }  
  Bar* bar() { return const_cast<Bar*>(cthis().bar()); } // use const case
private:
  const Foo & cthis() const { return *this; } 
};
于 2009-08-26T11:09:41.737 に答える
3

あなたはこのようなことをすることができます:

class Bar;

class Foo {
public:
  const Bar* bar() const { return getBar(); }

  Bar* bar() {
   return getBar();
  }

  private:
    Bar* getBar() const {/* Actual code */ return NULL;}
};
于 2009-08-26T11:01:32.407 に答える
2

私の個人的な感想は、これをたくさんやっていると、デザインに少し疑わしいことがあるということです。私が似たようなことをしなければならなかったとき、私は通常、メソッドによってアクセスされるものを変更可能にしました。

于 2009-08-26T11:09:24.030 に答える
1

私も以前にこの痛みを感じました-本質的に、あなたはコンパイラに定数がを介して「伝播する」ことを伝えようとしていますbar()。残念ながら、私が知る限り、これを自動的に行う方法はありません...関数の2番目のバージョンを手動で作成する必要があります。

于 2009-08-26T11:04:14.710 に答える
1

参考までに-OPによって投稿されたコードは、ScottMeyersの「EffectiveC++-ThirdEdition」に記載されている推奨される方法です。アイテム#3を参照してください。

于 2009-08-27T01:52:21.083 に答える
1

少し頭を下げて思いついた答え。ただし、後で投稿するlitbの回答のアイデアを使用して改善できると思います。したがって、これまでの私の解決策は次のようになります。

class ConstOverloadAdapter {
protected:

  // methods returning pointers 

  template< 
    typename R, 
    typename T >
  R* ConstOverload(
    const R* (T::*mf)(void) const) {
      return const_cast< R* >(
        (static_cast< const T* >(this)->*mf)());
    }

  // and similar templates for methods with parameters 
  // and/or returning references or void
};

class Bar;

class Foo : public ConstOverloadAdapter {
public:
  const Bar* bar() const { 
    /* implementation */ }

  Bar* bar(void* = 0) {  // the dummy void* is only needed for msvc
                         // since it cannot distinguish method overloads
                         // based on cv-type. Not needed for gcc or comeau.
    return ConstOverload(&Foo::bar); }
};
于 2009-08-28T16:49:11.770 に答える
0

テクニックとしてconst-correctnessを受け入れると仮定すると、簡潔さよりもコンパイラーでチェックされたconst-correctnessを好むことを意味すると思います。したがって、コンパイラに2つのことをチェックさせます。

  1. 「this」が非定数の場合、返すポインタの参照先は、呼び出し元が安全に変更できます。
  2. 「this」がconstの場合、どのような作業を行っても「this」に対して非const操作は実行されません。

constバージョンがnon-constを呼び出す場合、(2)は得られません。非constバージョンがconstバージョンを呼び出し、結果をconst_castsした場合、(1)は得られません。たとえば、Barが実際charにであり、作成したコードが(場合によっては)文字列リテラルを返すことになります。これはコンパイルされますが(-Wwrite-stringsは警告を表示しません)、呼び出し元は文字列リテラルへの非constポインターになります。これは、「コンパイラでチェックされたconst-correctnessを好む」と矛盾します。

両方がヘルパーメンバー関数Bar *getBar() constを呼び出す場合、(1)と(2)の両方を取得します。しかし、そのヘルパー関数を記述できるのであれば、const Fooから返されたバーを変更してもまったく問題がないのに、そもそもconstバージョンとnon-constバージョンをいじくりまわしているのはなぜですか?場合によっては、実装の詳細によって、必要なアクセサーが1つだけであっても、2つのアクセサーを使用してインターフェースを実装していることがあります。そうしないと、ヘルパーを記述できないか、2つの関数を1つのヘルパーだけで置き換えることができます。

コードサイズが問題にならない限り、(1)と(2)の両方を実現する最善の方法は、コンパイラに実際に両方のケースを考慮させることだと思います。

struct Bar { int a; };
struct Foo {
          Bar *bar()       { return getBar<Bar>(this); }
    const Bar *bar() const { return getBar<const Bar>(this); }

    Bar *bar2() const { return getBar<Bar>(this); } // doesn't compile. Good.
    Bar *bar3() const { return getBar<const Bar>(this); } // likewise

    private:
    template <typename B, typename F>
    static B *getBar(F *self) {
        // non-trivial code, which can safely call other functions with 
        // const/non-const overloads, and we don't have to manually figure out
        // whether it's safe to const_cast the result.
        return &self->myBar;
    }
    Bar myBar;
};

operator[]オブジェクトが所有する配列にアクセスするようなコードが些細なものである場合は、コードを複製するだけです。ある時点で、上記の関数テンプレートは複製よりもコーディングの労力が少なく、その時点でテンプレートを使用します。

const_castアプローチは、巧妙で一見標準的ですが、コンパイラーによってチェックされたconst-correctnessよりも簡潔さを選択するため、役に立たないと思います。メソッド内のコードが簡単な場合は、複製できます。些細なことではない場合、const_castが実際に有効であることを確認するのはあなたやコードメンテナにとって簡単ではありません。

于 2009-08-26T13:40:50.570 に答える
-1

あなたのコードは、const bar()が実際に新しいBarを作成して返すことを意味します。そして、あなたがその多くをしているなら、それは独特だと思います。

私にとってより大きな問題は、メンバー関数の定数が参照メンバーと非ポインターメンバーに制限されていることです。(非const)ポインターメンバーがある瞬間、const関数は、ボックス上でconstであると主張していても、それらを変更できます。

Barインスタンスがメンバー変数である場合は、代わりに参照を返すことを検討してください。

于 2009-08-26T11:16:13.173 に答える
-1

Barのconst-nessはあなたのFooのconst-nessに影響しますか?それとも、const Bar*とBar*のバージョンを区別する方法がないため、それを行っているだけですか?この後者の場合、Bar *を返すbar()が1つだけあり、それで完了します。結局のところ、const Bar *を取るものは、とにかくBar*をうまく取ることができます。bar()メソッドの定数は、BarではなくFooに関するものであることを忘れないでください。

また、const-とnon-const Bar *の両方を提供し、Fooをnon-const、willy-nillyにすることに満足している場合、Foo(したがってbar()メソッド)のconstnessは明らかにそれほど重要ではありません、constバージョンとnon-constバージョンを入力しているだけです。その場合、非constバージョンを許可し、再び/ only /を提供するだけで、constのコンシューマーは喜んでnon-constを取得します。ちょうど、constとnon-constがそこに行く方法では、プログラムに大きな違いはないように見えます。

constFooオブジェクトがconstとnon-constBarの両方を返すことができるようにしたい場合は、bar()をconstにして、non-constBarを返すだけです。

わからない、本当の答えを出すにはもっと見なければならないだろう。

ただし、どの定数が本当に接続されているか、プログラムにconstFoosとnon-constFoosの両方が必要かどうかをよく考えてください。すべての可能性を気ままに許して、あなたはプログラムが何を必要とするかについて考えていなかったようです。

于 2009-08-26T16:38:25.393 に答える
-2

1万語以上の価値がある1枚の写真、つまり:

const Item*
Storage::SearchItemBySomething( const Something &something ) const
{
    const Item* pData = NULL;

    // Algorythm for searching.

    return pData;
}

Item*
Storage::SearchItemBySomething( const Something &something )
{
    return (Item*) ((const Storage*)this)->_SearchItemBySomething(something);
}

実際のコードからコピーおよび変更されました。一般的な考え方は、constメソッドを実装し、最初のメソッドを使用するnonconstメソッドを作成することです。「this」ポインタを「const」にキャストする必要があり、「const」を返す「constItem*」からキャストする必要があります。

CスタイルのキャストをC++スタイルのキャストに置き換えることができます

  • static_cast <Item *>(...)
  • const_cast <const Storage *>(this)
于 2009-08-26T11:26:10.237 に答える