287

class X内部メンバーへのアクセスを返したい場合、次のようなものがあるとします。

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    Z& Z(size_t index)
    {
        // massive amounts of code for validating index

        Z& ret = vecZ[index];

        // even more code for determining that the Z instance
        // at index is *exactly* the right sort of Z (a process
        // which involves calculating leap years in which
        // religious holidays fall on Tuesdays for
        // the next thousand years or so)

        return ret;
    }
    const Z& Z(size_t index) const
    {
        // identical to non-const X::Z(), except printed in
        // a lighter shade of gray since
        // we're running low on toner by this point
    }
};

2 つのメンバー関数X::Z()X::Z() const中括弧内のコードは同じです。これはコードの重複であり、複雑なロジックを持つ長い関数ではメンテナンスの問題が発生する可能性があります

このコードの重複を避ける方法はありますか?

4

21 に答える 21

221

詳細な説明については、 p.10 の「constconstメンバー関数での重複の回避」という見出しを参照してください。23、項目 3 の「const可能な限り使用する」、Effective C++の 3d ed by Scott Meyers、ISBN-13: 9780321334879。

代替テキスト

Meyers のソリューション (簡略化) は次のとおりです。

struct C {
  const char & get() const {
    return c;
  }
  char & get() {
    return const_cast<char &>(static_cast<const C &>(*this).get());
  }
  char c;
};

2 つのキャストと関数呼び出しは見苦しいかもしれませconstんが、オブジェクトが最初から存在しないことを意味するため、非メソッドでは正しいconstです。(マイヤーズはこれについて徹底的に議論しています。)

于 2008-09-23T21:24:13.750 に答える
72

はい、コードの重複を避けることができます。const メンバー関数を使用してロジックを作成し、非 const メンバー関数で const メンバー関数を呼び出し、戻り値を非 const 参照 (または関数がポインターを返す場合はポインター) に再キャストする必要があります。

class X
{
   std::vector<Z> vecZ;

public:
   const Z& z(size_t index) const
   {
      // same really-really-really long access 
      // and checking code as in OP
      // ...
      return vecZ[index];
   }

   Z& z(size_t index)
   {
      // One line. One ugly, ugly line - but just one line!
      return const_cast<Z&>( static_cast<const X&>(*this).z(index) );
   }

 #if 0 // A slightly less-ugly version
   Z& Z(size_t index)
   {
      // Two lines -- one cast. This is slightly less ugly but takes an extra line.
      const X& constMe = *this;
      return const_cast<Z&>( constMe.z(index) );
   }
 #endif
};

注:ロジックを非 const 関数に入れたり、const 関数に非 const 関数を呼び出させたりしないことが重要です。これにより、未定義の動作が発生する可能性があります。その理由は、定数クラスのインスタンスが非定数インスタンスとしてキャストされるためです。非 const メンバー関数は、クラスを誤って変更する可能性があり、C++ 標準では未定義の動作が発生すると述べています。

于 2008-09-23T20:48:04.197 に答える
37

Scott Meyers のソリューションは、tempate ヘルパー関数を使用して C++11 で改善できると思います。これにより、意図がより明確になり、他の多くのゲッターに再利用できます。

template <typename T>
struct NonConst {typedef T type;};
template <typename T>
struct NonConst<T const> {typedef T type;}; //by value
template <typename T>
struct NonConst<T const&> {typedef T& type;}; //by reference
template <typename T>
struct NonConst<T const*> {typedef T* type;}; //by pointer
template <typename T>
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference

template<typename TConstReturn, class TObj, typename... TArgs>
typename NonConst<TConstReturn>::type likeConstVersion(
   TObj const* obj,
   TConstReturn (TObj::* memFun)(TArgs...) const,
   TArgs&&... args) {
      return const_cast<typename NonConst<TConstReturn>::type>(
         (obj->*memFun)(std::forward<TArgs>(args)...));
}

このヘルパー関数は、次の方法で使用できます。

struct T {
   int arr[100];

   int const& getElement(size_t i) const{
      return arr[i];
   }

   int& getElement(size_t i) {
      return likeConstVersion(this, &T::getElement, i);
   }
};

最初の引数は常に this-pointer です。2 番目は、呼び出すメンバー関数へのポインターです。その後、関数に転送できるように、任意の量の追加の引数を渡すことができます。可変個引数テンプレートのため、これには C++11 が必要です。

于 2013-05-27T20:43:51.590 に答える
30

素敵な質問と素敵な答え。キャストを使用しない別のソリューションがあります。

class X {

private:

    std::vector<Z> v;

    template<typename InstanceType>
    static auto get(InstanceType& instance, std::size_t i) -> decltype(instance.get(i)) {
        // massive amounts of code for validating index
        // the instance variable has to be used to access class members
        return instance.v[i];
    }

public:

    const Z& get(std::size_t i) const {
        return get(*this, i);
    }

    Z& get(std::size_t i) {
        return get(*this, i);
    }

};

ただし、静的メンバーを必要とし、そのinstance内部で変数を使用する必要があるという醜さがあります。

このソリューションの考えられるすべての (マイナスの) 影響を考慮したわけではありません。もしあれば教えてください。

于 2013-12-08T11:16:32.523 に答える
22

マイヤーズよりも少し冗長ですが、私はこれを行うかもしれません:

class X {

    private:

    // This method MUST NOT be called except from boilerplate accessors.
    Z &_getZ(size_t index) const {
        return something;
    }

    // boilerplate accessors
    public:
    Z &getZ(size_t index)             { return _getZ(index); }
    const Z &getZ(size_t index) const { return _getZ(index); }
};

プライベート メソッドには、const インスタンスに対して非 const Z& を返すという望ましくないプロパティがあります。これがプライベートな理由です。プライベート メソッドは、外部インターフェイスの不変条件を壊す可能性があります (この場合、目的の不変条件は、「const オブジェクトは、それを介して取得されたオブジェクトへの参照を介して変更できない」ことです)。

コメントはパターンの一部であることに注意してください - _getZ のインターフェースは、それを呼び出すことは決して有効ではないことを指定しています (アクセサは別として、明らかに): いずれにせよそうすることに考えられる利点はありません。より小さく、より高速なコードが得られます。このメソッドを呼び出すことは、const_cast を使用してアクセサーの 1 つを呼び出すことと同等であり、それも行いたくないでしょう。エラーを明らかにするのが心配な場合 (それは公正な目標です)、_getZ の代わりに const_cast_getZ を呼び出します。

ところで、マイヤーズのソリューションに感謝します。私はそれに哲学的な反論はありません。ただし、個人的には、回線ノイズのように見えるメソッドよりも、制御された反復と、厳密に制御された特定の状況でのみ呼び出す必要があるプライベート メソッドを好みます。あなたの毒を選んで、それに固執してください。

[編集: ケビンは、_getZ が、getZ と同じ方法で const に特化した別のメソッド (たとえば、generateZ) を呼び出す可能性があることを正しく指摘しました。この場合、_getZ は const Z& を認識し、それを返す前に const_cast する必要があります。ボイラープレート アクセサーがすべてをポリシングするため、それでも安全ですが、安全であることがはっきりとはわかりません。さらに、それを行った後、常に const を返すように generateZ を変更する場合は、常に const を返すように getZ を変更する必要がありますが、コンパイラはそのことを通知しません。

コンパイラに関する後者のポイントは、Meyers の推奨パターンにも当てはまりますが、明白でない const_cast に関する最初のポイントはそうではありません。したがって、_getZ がその戻り値に const_cast を必要とすることが判明した場合、このパターンは Meyers のパターンよりも多くの値を失うと思います。マイヤーズに比べてデメリットもあるので、そういう場合はマイヤーズに乗り換えると思います。一方から他方へのリファクタリングは簡単です。無効なコードとボイラープレートのみが _getZ を呼び出すため、クラス内の他の有効なコードには影響しません。]

于 2008-09-23T22:02:52.287 に答える
8

テンプレートを使用してこれを解決することもできます。このソリューションは少し醜いですが (ただし、醜さは .cpp ファイルに隠されています)、constness のコンパイラ チェックを提供し、コードの重複はありません。

.h ファイル:

#include <vector>

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    const std::vector<Z>& GetVector() const { return vecZ; }
    std::vector<Z>& GetVector() { return vecZ; }

    Z& GetZ( size_t index );
    const Z& GetZ( size_t index ) const;
};

.cpp ファイル:

#include "constnonconst.h"

template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
    // ... massive amounts of code ...

    // Note you may only use methods of X here that are
    // available in both const and non-const varieties.

    Child& ret = parent->GetVector()[index];

    // ... even more code ...

    return ret;
}

Z& X::GetZ( size_t index )
{
    return GetZImpl< X*, Z >( this, index );
}

const Z& X::GetZ( size_t index ) const
{
    return GetZImpl< const X*, const Z >( this, index );
}

私が見ることができる主な欠点は、メソッドのすべての複雑な実装がグローバル関数にあるため、上記の GetVector() などのパブリックメソッドを使用して X のメンバーを取得する必要があることです (常にconst および非 const バージョン) またはこの関数を友達にすることができます。でも友達は好きじゃない。

[編集: テスト中に追加された cstdio の不要なインクルードを削除しました。]

于 2009-01-13T16:21:19.777 に答える
5

constキャストが気に入らない場合は、別の回答で提案されているこの C++17 バージョンのテンプレート静的ヘルパー関数と、オプションの SFINAE テストを使用します。

#include <type_traits>

#define REQUIRES(...)         class = std::enable_if_t<(__VA_ARGS__)>
#define REQUIRES_CV_OF(A,B)   REQUIRES( std::is_same_v< std::remove_cv_t< A >, B > )

class Foobar {
private:
    int something;

    template<class FOOBAR, REQUIRES_CV_OF(FOOBAR, Foobar)>
    static auto& _getSomething(FOOBAR& self, int index) {
        // big, non-trivial chunk of code...
        return self.something;
    }

public:
    auto& getSomething(int index)       { return _getSomething(*this, index); }
    auto& getSomething(int index) const { return _getSomething(*this, index); }
};

フルバージョン: https://godbolt.org/z/mMK4r3

于 2019-12-05T18:23:53.257 に答える
3

ロジックをプライベート メソッドに移動し、getter 内で「参照を取得して返す」ことだけを行うのはどうですか? 実際、私は単純な getter 関数内の static キャストと const キャストについてかなり混乱しており、非常にまれな状況を除いて、それは醜いと思います!

于 2008-09-23T22:05:02.873 に答える
1

次のようなプライベート ヘルパー静的関数テンプレートをお勧めします。

class X
{
    std::vector<Z> vecZ;

    // ReturnType is explicitly 'Z&' or 'const Z&'
    // ThisType is deduced to be 'X' or 'const X'
    template <typename ReturnType, typename ThisType>
    static ReturnType Z_impl(ThisType& self, size_t index)
    {
        // massive amounts of code for validating index
        ReturnType ret = self.vecZ[index];
        // even more code for determining, blah, blah...
        return ret;
    }

public:
    Z& Z(size_t index)
    {
        return Z_impl<Z&>(*this, index);
    }
    const Z& Z(size_t index) const
    {
        return Z_impl<const Z&>(*this, index);
    }
};
于 2015-10-07T14:25:20.647 に答える
1

const/non-const 関数のペアを自動的に生成するマクロを思いつきました。

class A
{
    int x;    
  public:
    MAYBE_CONST(
        CV int &GetX() CV {return x;}
        CV int &GetY() CV {return y;}
    )

    //   Equivalent to:
    // int &GetX() {return x;}
    // int &GetY() {return y;}
    // const int &GetX() const {return x;}
    // const int &GetY() const {return y;}
};

実装については、回答の最後を参照してください。

の引数MAYBE_CONSTが重複しています。最初のコピーでCVは、何も置き換えられません。2 番目のコピーでは、 に置き換えられconstます。

CVマクロ引数に表示できる回数に制限はありません。

少し不便な点はありますが。が括弧内にある場合CV、この括弧のペアには次のプレフィックスを付ける必要がありますCV_IN

// Doesn't work
MAYBE_CONST( CV int &foo(CV int &); )

// Works, expands to
//         int &foo(      int &);
//   const int &foo(const int &);
MAYBE_CONST( CV int &foo CV_IN(CV int &); )

実装:

#define MAYBE_CONST(...) IMPL_CV_maybe_const( (IMPL_CV_null,__VA_ARGS__)() )
#define CV )(IMPL_CV_identity,
#define CV_IN(...) )(IMPL_CV_p_open,)(IMPL_CV_null,__VA_ARGS__)(IMPL_CV_p_close,)(IMPL_CV_null,

#define IMPL_CV_null(...)
#define IMPL_CV_identity(...) __VA_ARGS__
#define IMPL_CV_p_open(...) (
#define IMPL_CV_p_close(...) )

#define IMPL_CV_maybe_const(seq) IMPL_CV_a seq IMPL_CV_const_a seq

#define IMPL_CV_body(cv, m, ...) m(cv) __VA_ARGS__

#define IMPL_CV_a(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_b)
#define IMPL_CV_b(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_a)

#define IMPL_CV_const_a(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_b)
#define IMPL_CV_const_b(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_a)

サポートしていない C++20 より前の実装CV_IN:

#define MAYBE_CONST(...) IMPL_MC( ((__VA_ARGS__)) )
#define CV ))((

#define IMPL_MC(seq) \
    IMPL_MC_end(IMPL_MC_a seq) \
    IMPL_MC_end(IMPL_MC_const_0 seq)

#define IMPL_MC_identity(...) __VA_ARGS__
#define IMPL_MC_end(...) IMPL_MC_end_(__VA_ARGS__)
#define IMPL_MC_end_(...) __VA_ARGS__##_end

#define IMPL_MC_a(elem) IMPL_MC_identity elem IMPL_MC_b
#define IMPL_MC_b(elem) IMPL_MC_identity elem IMPL_MC_a
#define IMPL_MC_a_end
#define IMPL_MC_b_end

#define IMPL_MC_const_0(elem)       IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a(elem) const IMPL_MC_identity elem IMPL_MC_const_b
#define IMPL_MC_const_b(elem) const IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a_end
#define IMPL_MC_const_b_end
于 2019-10-19T17:43:08.630 に答える
0

通常、constバージョンとnon-constバージョンが必要なメンバー関数は、ゲッターとセッターです。ほとんどの場合、それらはワンライナーであるため、コードの重複は問題になりません。

于 2008-09-23T21:11:44.763 に答える
0

...の使用を正当に正当化した友人のためにこれを行いましたconst_cast... それを知らなかった場合、おそらく次のようなことをしたでしょう(あまりエレガントではありません):

#include <iostream>

class MyClass
{

public:

    int getI()
    {
        std::cout << "non-const getter" << std::endl;
        return privateGetI<MyClass, int>(*this);
    }

    const int getI() const
    {
        std::cout << "const getter" << std::endl;
        return privateGetI<const MyClass, const int>(*this);
    }

private:

    template <class C, typename T>
    static T privateGetI(C c)
    {
        //do my stuff
        return c._i;
    }

    int _i;
};

int main()
{
    const MyClass myConstClass = MyClass();
    myConstClass.getI();

    MyClass myNonConstClass;
    myNonConstClass.getI();

    return 0;
}
于 2015-06-16T13:13:06.670 に答える
-1

この DDJ の記事では、const_cast を使用する必要のないテンプレートの特殊化を使用する方法を示しています。ただし、このような単純な関数の場合、実際には必要ありません。

boost::any_cast (ある時点で、それはもうありません) は、非 const バージョンを呼び出す const バージョンからの const_cast を使用して、重複を回避します。ただし、非 const バージョンに const セマンティクスを課すことはできないため、非常に注意する必要があります。

最終的には、2 つのスニペットが直接重なっている限り、コードの重複は問題ありません。

于 2008-09-23T20:53:47.723 に答える
-1

jwfearn と kevin が提供するソリューションに追加するために、関数が shared_ptr を返す場合の対応するソリューションを次に示します。

struct C {
  shared_ptr<const char> get() const {
    return c;
  }
  shared_ptr<char> get() {
    return const_pointer_cast<char>(static_cast<const C &>(*this).get());
  }
  shared_ptr<char> c;
};
于 2014-11-20T14:06:10.343 に答える