web-dev-qa-db-ja.com

const-referenceでstd :: functionを渡す必要がありますか?

std::functionを取る関数があるとしましょう:

void callFunction(std::function<void()> x)
{
    x();
}

代わりにxをconst-referenceで渡す必要がありますか?:

void callFunction(const std::function<void()>& x)
{
    x();
}

この質問に対する答えは、関数がそれをどうするかによって変わりますか?たとえば、std::functionをメンバー変数に格納または初期化するクラスメンバー関数またはコンストラクターである場合。

120
Sven Adbring

パフォーマンスが必要な場合は、保存する場合は値で渡します。

「UIスレッドでこれを実行する」という関数があるとします。

std::future<void> run_in_ui_thread( std::function<void()> )

「ui」スレッドでコードを実行し、完了時にfutureを通知します。 (UIスレッドがUI要素を台無しにすることになっているUIフレームワークで有用です)

検討している2つの署名があります。

std::future<void> run_in_ui_thread( std::function<void()> ) // (A)
std::future<void> run_in_ui_thread( std::function<void()> const& ) // (B)

現在、これらを次のように使用する可能性があります。

run_in_ui_thread( [=]{
  // code goes here
} ).wait();

匿名のクロージャー(ラムダ)を作成し、その中からstd::functionを構築し、run_in_ui_thread関数に渡し、メインスレッドでの実行が完了するのを待ちます。

(A)の場合、std::functionはラムダから直接構築され、run_in_ui_thread内で使用されます。ラムダはmovedからstd::functionに変換されるため、移動可能な状態はすべて効率的に保持されます。

2番目の場合、一時的なstd::functionが作成され、ラムダはそれにmovedされ、その一時的なstd::functionrun_in_ui_thread内で参照によって使用されます。

これまでのところ、非常に良い-それらの2つは同じように機能します。 run_in_ui_threadがその関数引数のコピーを作成して、実行するためにUIスレッドに送信する場合を除きます! (処理が完了する前に戻るため、参照を使用することはできません)。ケース(A)の場合は、movestd::functionだけで長期保存します。 (B)の場合、std::functionのコピーを強制されます。

そのストアは、値渡しをより最適にします。 std::functionのコピーを保存している可能性がある場合は、値で渡します。それ以外の場合、どちらの方法もほぼ同等です。値によるマイナス面は、同じかさばるstd::functionを使用し、サブメソッドを次々に使用する場合のみです。それを除いて、moveconst&と同じくらい効率的です。

ここで、std::function内に永続的な状態がある場合、主に起動する2つの間にいくつかの違いがあります。

std::functionにはoperator() constを持つオブジェクトが格納されますが、変更するmutableデータメンバもあると仮定します(失礼です!)。

std::function<> const&の場合、変更されたmutableデータメンバーは、関数呼び出しから伝達されます。 std::function<>の場合、そうではありません。

これは比較的奇妙なコーナーケースです。

std::functionを、他のおそらく重量があり、安価に移動できるタイプと同じように扱います。移動は安価で、コピーには費用がかかります。

パフォーマンスが心配で、仮想メンバー関数を定義していない場合は、std::functionを使用しないでください。

ファンクタータイプをテンプレートパラメーターにすると、ファンクターロジックのインライン化など、std::functionよりも大きな最適化が可能になります。これらの最適化の効果は、std::functionを渡す方法に関するcopy-vs-indirectionの懸念を大きく上回る可能性があります。

もっと早く:

template<typename Functor>
void callFunction(Functor&& x)
{
    x();
}
31
Ben Voigt

C++ 11での通常のように、value/reference/const-referenceによる受け渡しは、引数をどうするかによって異なります。 std::functionも同様です。

値渡しを使用すると、引数を変数(通常はクラスのメンバー変数)に移動できます。

struct Foo {
    Foo(Object o) : m_o(std::move(o)) {}

    Object m_o;
};

関数が引数を移動することがわかっている場合、これが最良の解決策です。これにより、ユーザーは関数の呼び出し方法を制御できます。

Foo f1{Object()};               // move the temporary, followed by a move in the constructor
Foo f2{some_object};            // copy the object, followed by a move in the constructor
Foo f3{std::move(some_object)}; // move the object, followed by a move in the constructor

(非)const-referencesのセマンティクスを既に知っていると思いますので、その点については説明しません。これについての説明を追加する必要がある場合は、お問い合わせください。更新します。

23
syam