15

C ++のクラスでアルゴリズムをラップするときに、constの正確性に問題が生じることがよくあります。許可されていませんが、可変関数が欲しいと思います。次のようなクラスの実装方法を教えてもらえますか?

以下は私が書き たいコードです。

  • run()関数は、データを変更するため、const関数であってはなりません。
  • 関数get_result()は、データを返すため、(ユーザーに関する限り)定数関数である必要があります。

ただし、ユーザーがrun()を呼び出さずに結果を要求した場合は、get_result()関数でアルゴリズムを実行する必要があります。非const関数を呼び出すconst関数があるため、これはconstの正確性を損ないます。

class operate_on_data
{
  std::vector<double> m_data;  // the data to modify
  bool m_completed;  // check to see if the function run() has been called
public:
  operate_on_data(std::vector<double> data)
    : m_data(data), m_completed(false) {}  //initialise
  void run() //I don't want this function to be const  
  {
    //The algorithm goes here - it alters m_data.
    m_completed = true;  //the algorithm has been run
  }
  std::vector<double> get_result() const //I want this function to be const
  {
    /*The following breaks const correctness because 
      I am calling a non-const function from a const function
      - but this feels like the right thing to do ... */ 
    if (!m_completed) run();  //run the algorithm if it has not run already
    return m_data; //return
  }
};

上記のクラスをコンパイルすることができた唯一の方法は、次のいずれかです。

  • run()をconstにし、m_dataとm_completedを可変にします。これは機能しますが、run()が明らかにデータを変更するため、概念的には間違っています。
  • get_result()を定数関数にしないでください。これも間違っているように見えます。ユーザーは、この関数が単純な戻り値であり、したがって一定であると期待するからです。
  • run()関数をget_result()const関数に入れ、データ変数を変更可能にします。

私の理解では、mutableキーワードはこれらのオプションの3番目用に設計されており、実装ではデータを変更する必要がありますが、ユーザーは単純な戻り値、したがってconst関数を合理的に期待しています。

ただし、ユーザーがデータを変更するタイミングを正確に選択できるようにするため、この最後のオプションは実行しません。ただし、run()の呼び出しを忘れる可能性があるため、run()を呼び出さずに結果を要求した場合は、アルゴリズムを強制したいと思います。run()を可変にしたいように感じますが、許可されていません。

そのようなクラスを書く正しい方法は何ですか?

4

8 に答える 8

11

run() を const にし、m_data と m_completed を変更可能にします。これは機能しますが、run() が明らかにデータを変更するため、概念的には間違っています。

実際にはそうではありません。実際、クラス内の変数は変更されていますが、これを実証することはできません。run() を呼び出しても、ユーザーがクラスのインターフェースから取得できるものは何も変更されません。そのような変更に関する情報を取得できない場合、その変更を実証することはできません。これはセマンティックの問題ではなく、'mutable' キーワードの全体的な要点を直接物語っています。

'mutable' キーワードは大きく誤解されています。

そうは言っても、私が持っている最小限の情報で上記の方法で実行できるかもしれませんが、お勧めしません。問題の全体像を考えると、明らかなより良い方法がほぼ確実にあります。

私が使用する可能性のある他の方法は、明らかに回避するように設定されているものです。ユーザーに get_data() を使用する前に run() を呼び出すように強制します。実を言うと、これも最適とは言えない方法です。おそらくもっとそうです。

編集:

変更可能な方法を使用することにした場合は、いくつかの変更をお勧めします。const であり、何も返さない 'run()' という関数があると、かなり混乱します。この関数は確かに非 const であるべきです。したがって、すでにこの方法で実行するという決定が与えられた場合、run() に、現在の「run()」関数の動作を持つ const およびプライベート関数を呼び出させることです。これは get_data( によっても参照されます) ) 指定された条件下で。

于 2010-11-22T18:20:24.653 に答える
6

あなたが物事を明確にするのを助けるかもしれないいくつかの抽象的な発言:

  • constメソッドは、オブジェクトの概念的な「状態」を変更しないメソッドです。
  • const方法はそうするものです。
  • さらに、mutableフィールドはオブジェクトごとのフィールドですが、オブジェクトの概念の一部とは見なされませんstate(遅延評価されて記憶される一部のキャッシュ値など)。

問題は、operate_on_data実際には明確に定義されたクラスではない可能性があることです。クラス「operate_on_data」のオブジェクトは何ですか?このオブジェクトの「状態」は何ですか?何ではないのですか?これは(少なくとも私には)ぎこちなく聞こえます-そして、いくつかのデザインのぎこちない響きの説明は、直感に反するデザインを示している可能性があります。

私の考えでは、「操作」と「操作結果」の異なる概念を1つの奇妙なクラスに保持しているため、混乱が生じます。

于 2010-11-22T18:07:23.517 に答える
3

あなたの問題は意味論的なものであり、構文的なものではないと思います。

run()私の目には、最初に呼び出さずに結果を要求することはエラーであり、例外が発生するはずです。

エラーではなく、実際に可能であるはずの場合、そもそも意味がないrun()ので、それをドロップして、(非定数)ですべての作業を実行しますget_result()

于 2010-11-22T18:06:11.653 に答える
2

アルゴリズムを実行する前に結果を取得するには、エラー (インターフェースの一部) にするだけです。次に、作業を結果から分離し、両方が一貫性を適切に示すことができるようにします。アルゴリズムを実行する前に get メソッドを呼び出して、クライアントが何か間違ったことをしていることをクライアントに示すと、get メソッドがスローされる可能性があります。

于 2010-11-22T18:03:27.517 に答える
1

get_result()実際にデータを変更する可能性がある場合、それは定数ではありません。constにしたい場合は、呼び出さずrun()に例外をスローします。

mutableキャッシュされたデータ、つまりインスタンスの状態を変更せず、パフォーマンス上の理由でのみ保存されるデータに使用する必要があります。

于 2010-11-22T18:06:57.227 に答える
0

もし私がその位置に来たら、おそらく例外を投げるでしょう。

しかし、あなた逃げることができます

if (!m_completed) (const_cast <operate_on_data *> (this))->run();

ただし、実際にと定義されているget_resultインスタンスでが呼び出された場合は、lala-landに入ります。operate_on_dataconst

于 2010-11-22T18:07:12.317 に答える
0

run()オブジェクト内で変更されるのが だけである場合は、 andm_completedを宣言しても問題ありません。他のものを変更する場合、呼び出しはそれらの他のものも変更します。意味は絶対にすべきではありません。m_completed mutablerun() construn()get_result()get_result()const

ただし、説明を締めくくるために、STL コンテナーにはそれぞれ 2 つのbegin()関数と 2 つの関数があることに気付くでしょうend()。1begin()つの関数と 1 つのend()関数は変更可能な反復子を返し、もう 1 つの関数begin()end()関数は s を返しconst_iteratorます。

run()実際、aconstと非constバージョンでオーバーロードすることは可能です。次に、唯一の有効なオプションと見なされるため、 のバージョンget_result()を呼び出します。construn()

class operate_on_data
{
    std::vector<double> m_data;
    bool m_completed;
public:
    operate_on_data(std::vector<double> data)
        : m_data(data), m_completed(false) { }
    void run()
    {
       //The algorithm goes here - it alters m_data.
       m_completed = true;
    }

    void run() const
    {
        // something that does not modify m_data or m_completed
    }
    std::vector<double> get_result() const
    {
        if (!m_completed)
            run();
        return m_data;
    }
};

ただし、これは、 のconstバージョンがrun()状態を変更しない場合にのみ意味があります。さもなければ、意志の非construn()が漏れ出してget_result()const露骨get_result()な嘘になる。


サンプルコードは多少不自然だと思います。そうでない場合は、基本的にこれを取っています:

std::vector<double> results = do_calculation(data);

get_results()そして、それを非常に薄いインターフェース (つまり、を返すメソッド)を持つオブジェクトにラップしますstd::vector<double>。オブジェクト化されたバージョンではあまり改善が見られません。std::vector<double>計算の結果をキャッシュしたい場合は、通常、このオブジェクトを作成して使用するコードの around int を単純に保持することで、これを行うのが非常に理にかなっています。

于 2010-11-22T18:28:54.347 に答える