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


141

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

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

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

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

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


1
おそらく違います。はっきりとはわかりませんが、がこれsizeof(std::function)以上ないことを期待します2 * sizeof(size_t)
Mats Petersson 2013

12
@マット:std::functionラッパーのサイズは、コピーの複雑さほど重要ではないと思います。深いコピーが含まれている場合、sizeof提案よりもはるかに高価になる可能性があります。
Ben Voigt 2013

あなたmoveは関数を使うべきですか?
Yakk-Adam Nevraumont 2013

operator()()あるconstconst参照が動作するはずですので。しかし、私はstd :: functionを使用したことがありません。
Neel Basu

@Yakkラムダを関数に直接渡します。
Sven Adbring 2013

回答:


79

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

「これを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し、それからoutを作成し、それをrun_in_ui_thread関数に渡し、メインスレッドで実行が完了するのを待ちます。

(A)の場合、std::functionはラムダから直接作成され、内で使用されますrun_in_ui_thread。ラムダはにmove組み込まれているstd::functionので、あらゆる可動状態が効率的にそこに運ばれます。

2番目のケースでは、一時的な std::functionが作成され、ラムダがmoveそれに挿入され、そのテンポラリstd::functionが内で参照によって使用されますrun_in_ui_thread

これまでのところ、非常に優れています。2つは同じように動作します。を除いて、run_in_ui_thread実行するためにuiスレッドに送信する関数引数のコピーを作成します!(処理が完了する前に戻るため、参照を使用することはできません)。ケース(A)の場合、単純にmovestd::functionその長期保存に。ケース(B)では、をコピーする必要がありstd::functionます。

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

さて、2つの間には他にもいくつかの違いがあります。これらの違いは、内に永続的な状態がある場合に主に発生しstd::functionます。

は、std::functionいくつかのオブジェクトをで保存するとしますが、変更するデータメンバーoperator() constもいくつか持っているものとしmutableます(失礼です)。

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

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

あなたstd::functionは、他のおそらくヘビー級の安価な可動タイプと同じように扱いたいです。移動は安く、コピーは高価になる可能性があります。


「格納する場合は値渡し」のセマンティックな利点は、あなたが言うように、コントラクトによって、渡された引数のアドレスを関数が保持できないことです。しかし、「それを除けば、移動はconst&と同じくらい効率的だ」というのは本当ですか?コピー操作のコストと移動操作のコストが常に表示されます。通り過ぎるconst&と、コピー操作のコストのみが表示されます。
ceztko

2
@ceztko(A)と(B)のどちらの場合でも、一時ファイルstd::functionはラムダから作成されます。(A)では、一時変数はへの引数に省略されていますrun_in_ui_thread。(B)では、一時ファイルへの参照がに渡されrun_in_ui_threadます。限り、あなたのようstd::functionsは一時としてラムダから作成され、その句が成り立ちます。前の段落では、がstd::function持続する場合について説明しました。我々は場合はされていないだけで、ラムダから作成、保存、function const&およびfunctionまったく同じオーバーヘッドを持っています。
Yakk-Adam Nevraumont 2017年

ああ、なるほど!もちろん、これはの外部で何が発生するかによって異なりrun_in_ui_thread()ます。「参照渡しですが、住所は保存しません」という署名だけがありますか?
ceztko

@ceztkoいいえ、ありません。
Yakk-Adam Nevraumont 2017年

1
@ Yakk-AdamNevraumontは、右辺値参照で渡す別のオプションをカバーするためにより完全な場合:std::future<void> run_in_ui_thread( std::function<void()>&& )
Pavel P

33

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

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

もっと早く:

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

1
実はパフォーマンスについては全然気になりません。const-referencesを使用する必要がある場所で使用するのが一般的な方法だと思いました(文字列とベクトルが思い浮かびます)。
Sven Adbring 2013

13
@ベン:私はこれを実装する最も近代的なヒッピーフレンドリーな方法は使用することだと思います std::forward<Functor>(x)();は、ファンクタの値カテゴリを保持にを「ユニバーサル」リファレンスだからです。ただし、99%のケースでは違いはありません。
GManNickG 2013

1
@Ben Voigtなので、あなたの場合、移動で関数を呼び出しますか?callFunction(std::move(myFunctor));
arias_JC

2
@arias_JC:パラメータがラムダの場合、それはすでに右辺値です。左辺値がある場合は、std::move他の方法で不要になった場合に使用するか、既存のオブジェクトから移動したくない場合は直接渡すことができます。参照を折りたたむルールcallFunction<T&>()は、がT&ではなくタイプのパラメーターを持っていることを確認しますT&&
Ben Voigt 2017

1
@BoltzmannBrain:関数が1回だけ呼び出されるという最も単純な場合にのみ有効であるため、変更しないことを選択しました。私の答えは、「関数オブジェクトをどのように渡せばよいのか」という質問です。そして、無条件にそのファンクタを正確に一度だけ呼び出す以外に何もしない関数に限定されません。
Ben Voigt

25

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のセマンティクスをすでに知っていると思いますので、私は要点を説明しません。これについての説明を追加する必要がある場合は、質問してください。更新します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.