c++ - tipos - Pasar un objeto de cierre no copiable a std:: function parámetro
pasos para asignar un parametro en c++ (3)
En C ++ 14, una expresión lambda puede capturar variables moviéndose desde ellas utilizando inicializadores de captura. Sin embargo, esto hace que el objeto de cierre resultante no se pueda copiar. Si tengo una función existente que toma un argumento std::function
(que no puedo cambiar), no puedo pasar el objeto de cierre, porque el constructor de std::function
requiere que el functor dado sea 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; });
}
Esto da los siguientes errores:
/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;
^
¿Hay una solución razonable?
Probando con Ubuntu clang versión 3.5-1 ~ exp1 (troncal)
Hay este enfoque:
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) );
}
donde hacemos un puntero compartido a nuestros datos, luego hacemos un lambda copiable que captura ese puntero compartido, luego envolvemos ese lambda copiable en una std::function
de la firma solicitada.
En su caso anterior, usted simplemente:
doit( make_copyable_function<void()>( [p = std::move(p)] () { std::cout << *p << std::endl; } ) );
Una versión ligeramente más avanzada difiere el borrado de tipo y agrega una capa de reenvío perfecto para reducir la sobrecarga:
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)};
}
lo que no requiere que usted pase la firma, y puede ser un poco más eficiente en algunos casos, pero utiliza técnicas más oscuras.
En C ++ 14 con esto se puede hacer aún más breve:
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)... );
};
}
Eliminar la necesidad del tipo de ayudante por completo.
Si la vida útil del objeto de cierre no es un problema, puede pasarlo en un contenedor de referencia:
int main()
{
std::unique_ptr<int> p(new int(5));
auto f = [p = std::move(p)]{
std::cout << *p << std::endl;
};
doit(std::cref(f));
}
Obviamente, esto no se aplica a todos los escenarios, pero está bien para su programa de ejemplo.
EDITAR: Echando un vistazo a N3797 (borrador de trabajo de C ++ 14) § 20.9.11.2.1 [func.wrap.func.con] p7, el requisito CopyConstructible
todavía está allí. Me pregunto si hay una razón técnica que no se puede aflojar a MoveConstructible
, o si el comité simplemente no lo logró.
EDIT: Respondiendo a mi propia pregunta: la std::function
es CopyConstructible
, por lo que el functor envuelto también necesita ser CopyConstructible
.
Si sabe que realmente no va a copiar su objeto de función, entonces puede envolverlo en un tipo que haga que el compilador piense que se puede copiar:
struct ThrowOnCopy {
ThrowOnCopy() = default;
ThrowOnCopy(const ThrowOnCopy&) { throw std::logic_error("Oops!"); }
ThrowOnCopy(ThrowOnCopy&&) = default;
ThrowOnCopy& operator=(ThrowOnCopy&&) = default;
};
template<typename T>
struct FakeCopyable : ThrowOnCopy
{
FakeCopyable(T&& t) : target(std::forward<T>(t)) { }
FakeCopyable(FakeCopyable&&) = default;
FakeCopyable(const FakeCopyable& other)
: ThrowOnCopy(other), // this will throw
target(std::move(const_cast<T&>(other.target))) // never reached
{ }
template<typename... Args>
auto operator()(Args&&... a)
{ return target(std::forward<Args>(a)...); }
T target;
};
template<typename T>
FakeCopyable<T>
fake_copyable(T&& t)
{ return { std::forward<T>(t) }; }
// ...
doit( fake_copyable([p = std::move(p)] () { std::cout << *p << std::endl; }) );
La plantilla de función fake_copyable
crea un contenedor que es CopyConstructible
acuerdo con el compilador (y <type_traits>
) pero no se puede copiar en tiempo de ejecución.
Si almacena un FakeCopyable<X>
en una std::function
y luego termina copiando la std::function
obtendrá un std::logic_error
lanzado, pero si solo mueve la std::function
todo funcionará bien.
El target(std::move(const_cast<T&>(other.target)))
parece preocupante, pero ese inicializador nunca se ejecutará, porque el inicializador de clase base se lanzará primero. Así que el preocupado const_cast
nunca sucede realmente, solo mantiene contento al compilador.