コピー不可能なクロージャオブジェクトをstd::functionパラメータに渡す [重複] 質問する

コピー不可能なクロージャオブジェクトをstd::functionパラメータに渡す [重複] 質問する

C++14 では、ラムダ式はキャプチャ初期化子を使用して変数から移動することで変数をキャプチャできます。ただし、これにより、結果のクロージャ オブジェクトはコピー不可能になります。引数std::function(変更できない) を取る既存の関数がある場合、std::functionのコンストラクタでは指定されたファンクタが である必要があるため、クロージャ オブジェクトを渡すことはできませんCopyConstructible

#include <iostream>
#include <memory>

void doit(std::function<void()> f) {
    f();
}

int main()
{
    std::unique_ptr<int> p(new int(5));
    doit([p = std::move(p)] () { std::cout << *p << std::endl; });
}

これにより、次のエラーが発生します。

/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1911:10: error: 
      call to implicitly-deleted copy constructor of '<lambda at test.cpp:10:7>'
            new _Functor(*__source._M_access<_Functor*>());
                ^        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1946:8: note: in
      instantiation of member function 'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7>
      >::_M_clone' requested here
              _M_clone(__dest, __source, _Local_storage());
              ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2457:33: note: in
      instantiation of member function 'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7>
      >::_M_manager' requested here
            _M_manager = &_My_handler::_M_manager;
                                       ^
test.cpp:10:7: note: in instantiation of function template specialization 'std::function<void
      ()>::function<<lambda at test.cpp:10:7>, void>' requested here
        doit([p = std::move(p)] () { std::cout << *p << std::endl; });
             ^
test.cpp:10:8: note: copy constructor of '' is implicitly deleted because field '' has a deleted
      copy constructor
        doit([p = std::move(p)] () { std::cout << *p << std::endl; });
              ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/unique_ptr.h:273:7: note: 
      'unique_ptr' has been explicitly marked deleted here
      unique_ptr(const unique_ptr&) = delete;
      ^

合理的な回避策はありますか?

Ubuntu clang バージョン 3.5-1~exp1 (trunk) でテスト中

ベストアンサー1

次のようなアプローチがあります:

template< typename signature >
struct make_copyable_function_helper;
template< typename R, typename... Args >
struct make_copyable_function_helper<R(Args...)> {
  template<typename input>
  std::function<R(Args...)> operator()( input&& i ) const {
    auto ptr = std::make_shared< typename std::decay<input>::type >( std::forward<input>(i) );
    return [ptr]( Args... args )->R {
      return (*ptr)(std::forward<Args>(args)...);
    };
  }
};

template< typename signature, typename input >
std::function<signature> make_copyable_function( input && i ) {
  return make_copyable_function_helper<signature>()( std::forward<input>(i) );
}

ここで、データへの共有ポインターを作成し、その共有ポインターをキャプチャするコピー可能なラムダを作成し、そのコピー可能なラムダをstd::function要求されたシグネチャの にラップします。

上記のケースでは、次のようになります。

doit( make_copyable_function<void()>( [p = std::move(p)] () { std::cout << *p << std::endl; } ) );

もう少し高度なバージョンでは、型の消去を延期し、完全な転送のレイヤーを追加してオーバーヘッドを削減します。

template<typename input>
struct copyable_function {
  typedef typename std::decay<input>::type stored_input;
  template<typename... Args>
  auto operator()( Args&&... args )->
    decltype( std::declval<input&>()(std::forward<Args>(args)...) )
  {
    return (*ptr)(std::forward<Args>(args));
  }
  copyable_function( input&& i ):ptr( std::make_shared<stored_input>( std::forward<input>(i) ) ) {}
  copyable_function( copyable_function const& ) = default;
private:
  std::shared_ptr<stored_input> ptr;
};
template<typename input>
copyable_function<input> make_copyable_function( input&& i ) {
  return {std::forward<input>(i)}; 
}

署名を渡す必要がなく、いくつかのケースでは若干効率が上がりますが、よりわかりにくい手法が使用されます。

C++14 では、これをさらに簡潔にすることができます。

template< class F >
auto make_copyable_function( F&& f ) {
  using dF=std::decay_t<F>;
  auto spf = std::make_shared<dF>( std::forward<F>(f) );
  return [spf](auto&&... args)->decltype(auto) {
    return (*spf)( decltype(args)(args)... );
  };
}

ヘルパー型の必要性を完全に排除します。

おすすめ記事