web-dev-qa-db-ja.com

std :: functionをコピーする必要がありますか、それとも常に参照できますか?

C++アプリケーション(Visual Studio 2010を使用)で、次のようにstd :: functionを保存する必要があります。

class MyClass
   {
   public:
      typedef std::function<int(int)> MyFunction;
      MyClass (Myfunction &myFunction);
   private:
      MyFunction m_myFunction;    // Should I use this one?
      MyFunction &m_myFunction;   // Or should I use this one?
   };

ご覧のとおり、関数引数をコンストラクターの参照として追加しました。

しかし、関数をクラスに保存する最良の方法は何ですか?

  • 関数を参照として保存できますか?std :: functionは単なる関数ポインターであり、関数の「実行可能コード」はメモリ内にとどまることが保証されていますか?
  • ラムダが渡され、呼び出し元が戻る場合にコピーを作成する必要がありますか?

私の直感では、参照(const-referenceでさえ)を保存しても安全だと言っています。コンパイラは、コンパイル時にラムダのコードを生成し、アプリケーションの実行中にこの実行可能コードを「仮想」メモリに保持することを期待しています。したがって、実行可能コードが「削除」されることはなく、参照を安全に保存できます。しかし、これは本当に本当ですか?

59
Patrick

関数を参照として保存できますか?std :: functionは単なる関数ポインターであり、関数の「実行可能コード」はメモリ内にとどまることが保証されていますか?

_std::function_は、単なる関数ポインターではありません。これは、任意の呼び出し可能なオブジェクトのラッパーであり、そのオブジェクトを保存するために使用されるメモリを管理します。他のタイプと同様に、参照を保存しても安全ですifのみ参照が使用されるときは常に、参照されるオブジェクトが有効であることを保証する他の方法があります。

参照を保存する正当な理由と、参照が有効であることを保証する方法がない限り、値で保存してください。

const参照をコンストラクタに渡すことは安全で、おそらく値を渡すよりも効率的です。 const以外の参照で渡すことは、一時的なものを渡すことができないため、ユーザーはラムダ、bindの結果、またはその他の呼び出し可能オブジェクトを直接渡すことができないため、悪い考えです。 std::function<int(int)>自体を除くオブジェクト。

56
Mike Seymour

関数を参照によってコンストラクターに渡し、そのコピーを作成しない場合、参照が有効でなくなるため、関数がこのオブジェクトの範囲外に出ると運が悪くなります。そのことは以前の回答ですでに述べられています。

追加したかったのは、代わりに、参照ではなくvalueで関数をコンストラクターに渡すことができるということでした。どうして?とにかく、それのコピーが必要なので、値で渡す場合、コンパイラーは、一時ファイルが渡されたときにコピーを作成する必要性を最適化することができます(インプレースで記述されたラムダ式など)。

もちろん、どんなことをしても、渡された関数を変数に割り当てるときに別のコピーを作成する可能性があるため、std::moveそのコピーを削除します。例:

class MyClass
{
public:
   typedef std::function<int(int)> MyFunction;

   MyClass (Myfunction myFunction): m_myfunction(std::move(myFunction))
       {}

private:
   MyFunction m_myFunction;
};

したがって、上記に右辺値を渡すと、コンパイラは最初のコピーをコンストラクターに最適化し、std :: moveは2番目のコピーを削除します:)

(のみ)コンストラクターがconst参照を受け取る場合、willがどのように渡されるかに関係なく、関数内でそのコピーを作成する必要があります。

別の方法は、左辺値と右辺値を別々に処理する2つのコンストラクターを定義することです。

class MyClass
{
public:
   typedef std::function<int(int)> MyFunction;

   //takes lvalue and copy constructs to local var:
   MyClass (const Myfunction & myFunction): m_myfunction(myFunction)
       {}
   //takes rvalue and move constructs local var:
   MyClass (MyFunction && myFunction): m_myFunction(std::move(myFunction))
       {}

private:
   MyFunction m_myFunction;
};

これで、右辺値を異なる方法で手作業で処理し、明示的に処理することで(コンパイラに処理させるのではなく)その場合にコピーする必要がなくなります。最初のものよりわずかに効率的かもしれませんが、より多くのコードです。

(おそらくここでかなり見られる)関連参照(および非常に良い読み物): http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

14
jsdw

一般的なルールとして(特に、高度にスレッド化されたシステムにこれらを使用している場合)、値を渡します。スレッド内から、基礎となるオブジェクトがまだ参照型を保持していることを確認する方法はないため、非常に厄介な人種やデッドロックのバグにさらされます。

もう1つの考慮事項は、std :: function内の非表示の状態変数です。この場合、変更がスレッドセーフになることはほとんどありません。これは、基礎となる関数呼び出しがスレッドセーフであっても、std :: functionラッパーの「()」呼び出しがその周りにない場合があることを意味します。常にstd :: functionのスレッドローカルコピーを使用することで、目的の動作を回復できます。これは、それぞれが状態変数の分離されたコピーを持っているためです。

3
Zack Yezek

好きなだけコピーしてください。コピー可能です。標準ライブラリのほとんどのアルゴリズムでは、ファンクターが必要です。

ただし、重要なケースではおそらく参照渡しの方が高速になるので、ライフサイクル管理を気にする必要がないように、定数参照で渡し、値で保存することをお勧めします。そう:

class MyClass
{
public:
    typedef std::function<int(int)> MyFunction;
    MyClass (const Myfunction &myFunction);
          // ^^^^^ pass by CONSTANT reference.
private:
    MyFunction m_myFunction;    // Always store by value
};

定数または右辺値参照で渡すことにより、呼び出し元に、まだ呼び出すことができる間は関数を変更しないことを約束します。これにより、誤って関数を変更することを防ぎ、戻り値を使用するよりも読みにくいため、通常は意図的に実行することは避けてください。

編集:私はもともと「定数または右辺値」と言っていましたが、デイブのコメントで調べてみましたが、実際右辺値参照は左辺値を受け入れません。

2
Jan Hudec

コピーを作成することをお勧めします。

MyFunction m_myFunction; //prefferd and safe!

元のオブジェクトがそれ自体を破壊する範囲外に出ても、コピーはクラスインスタンスに存在するため、安全です。

2
Nawaz