Respostas:
No C ++ 14, teremos a chamada captura lambda generalizada . Isso permite a captura de movimento. O código a seguir será legal no C ++ 14:
using namespace std;
// a unique_ptr is move-only
auto u = make_unique<some_type>( some, parameters );
// move the unique_ptr into the lambda
go.run( [ u{move(u)} ] { do_something_with( u ); } );
Mas é muito mais geral no sentido de que variáveis capturadas podem ser inicializadas com algo assim:
auto lambda = [value = 0] mutable { return ++value; };
No C ++ 11 isso ainda não é possível, mas com alguns truques que envolvem tipos auxiliares. Felizmente, o compilador Clang 3.4 já implementa esse recurso incrível. O compilador será lançado em dezembro de 2013 ou janeiro de 2014, se o ritmo de lançamento recente for mantido.
ATUALIZAÇÃO: O compilador Clang 3.4 foi lançado em 6 de janeiro de 2014 com o recurso mencionado.
Aqui está uma implementação de uma função auxiliar make_rref
que ajuda na captura artificial de movimentos
#include <cassert>
#include <memory>
#include <utility>
template <typename T>
struct rref_impl
{
rref_impl() = delete;
rref_impl( T && x ) : x{std::move(x)} {}
rref_impl( rref_impl & other )
: x{std::move(other.x)}, isCopied{true}
{
assert( other.isCopied == false );
}
rref_impl( rref_impl && other )
: x{std::move(other.x)}, isCopied{std::move(other.isCopied)}
{
}
rref_impl & operator=( rref_impl other ) = delete;
T && move()
{
return std::move(x);
}
private:
T x;
bool isCopied = false;
};
template<typename T> rref_impl<T> make_rref( T && x )
{
return rref_impl<T>{ std::move(x) };
}
E aqui está um caso de teste para essa função que foi executada com êxito no meu gcc 4.7.3.
int main()
{
std::unique_ptr<int> p{new int(0)};
auto rref = make_rref( std::move(p) );
auto lambda =
[rref]() mutable -> std::unique_ptr<int> { return rref.move(); };
assert( lambda() );
assert( !lambda() );
}
A desvantagem aqui é que lambda
é copiável e, quando copiada, a asserção no construtor de cópia de rref_impl
falhas que leva a um erro de tempo de execução. A seguir pode ser uma solução melhor e ainda mais genérica porque o compilador detectará o erro.
Aqui está mais uma idéia sobre como implementar a captura lambda generalizada. O uso da função capture()
(cuja implementação é encontrada mais abaixo) é o seguinte:
#include <cassert>
#include <memory>
int main()
{
std::unique_ptr<int> p{new int(0)};
auto lambda = capture( std::move(p),
[]( std::unique_ptr<int> & p ) { return std::move(p); } );
assert( lambda() );
assert( !lambda() );
}
Aqui lambda
está um objeto functor (quase um lambda real) que capturou à std::move(p)
medida que é passado para capture()
. O segundo argumento de capture
é um lambda que aceita a variável capturada como argumento. Quando lambda
é usado como objeto de função, todos os argumentos passados a ele serão encaminhados para o lambda interno como argumentos após a variável capturada. (No nosso caso, não há mais argumentos a serem encaminhados). Essencialmente, o mesmo que na solução anterior acontece. Veja como capture
é implementado:
#include <utility>
template <typename T, typename F>
class capture_impl
{
T x;
F f;
public:
capture_impl( T && x, F && f )
: x{std::forward<T>(x)}, f{std::forward<F>(f)}
{}
template <typename ...Ts> auto operator()( Ts&&...args )
-> decltype(f( x, std::forward<Ts>(args)... ))
{
return f( x, std::forward<Ts>(args)... );
}
template <typename ...Ts> auto operator()( Ts&&...args ) const
-> decltype(f( x, std::forward<Ts>(args)... ))
{
return f( x, std::forward<Ts>(args)... );
}
};
template <typename T, typename F>
capture_impl<T,F> capture( T && x, F && f )
{
return capture_impl<T,F>(
std::forward<T>(x), std::forward<F>(f) );
}
Essa segunda solução também é mais limpa, porque desativa a cópia do lambda, se o tipo capturado não for copiável. Na primeira solução que só pode ser verificada em tempo de execução com um assert()
.
moveCapture
wrapper para passá-los como argumentos (esse método é usado acima e no Capn'Proto, uma biblioteca do criador de protobuffs) ou faça apenas aceitar que você precisa de compiladores que o suportem: P
Você também pode usar std::bind
para capturar o unique_ptr
:
std::function<void()> f = std::bind(
[] (std::unique_ptr<int>& p) { *p=4; },
std::move(myPointer)
);
unique_ptr
referência rvalue não pode ser ligada a um int *
.
myPointer
neste caso). Portanto, o código acima não compila no VS2013. Embora funcione bem no GCC 4.8.
Você pode obter a maior parte do que deseja usar std::bind
, assim:
std::unique_ptr<int> myPointer(new int{42});
auto lambda = std::bind([](std::unique_ptr<int>& myPointerArg){
*myPointerArg = 4;
myPointerArg.reset(new int{237});
}, std::move(myPointer));
O truque aqui é que, em vez de capturar seu objeto somente para movimentação na lista de capturas, fazemos disso um argumento e usamos o aplicativo parcial via std::bind
para fazê-lo desaparecer. Observe que o lambda toma isso por referência , porque na verdade é armazenado no objeto de ligação. Eu também adicionei código que escreve no objeto móvel real, porque é algo que você pode querer fazer.
No C ++ 14, você pode usar a captura lambda generalizada para obter os mesmos fins, com este código:
std::unique_ptr<int> myPointer(new int{42});
auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
*myPointerCapture = 56;
myPointerCapture.reset(new int{237});
};
Mas esse código não compra nada do que você não tinha no C ++ 11 via std::bind
. (Existem algumas situações em que a captura lambda generalizada é mais poderosa, mas não neste caso.)
Agora, há apenas um problema; você queria colocar essa função em std::function
, mas essa classe exige que a função seja CopyConstructible , mas não é, é apenas MoveConstructible porque está armazenando um std::unique_ptr
que não é CopyConstructible .
Você deve contornar o problema com a classe wrapper e outro nível de indireção, mas talvez você não precise std::function
. Dependendo das suas necessidades, você poderá usar std::packaged_task
; faria o mesmo trabalho que std::function
, mas não exige que a função seja copiável, apenas móvel (da mesma forma, std::packaged_task
é apenas móvel). A desvantagem é que, como se destina a ser usado em conjunto com std :: future, você pode chamá-lo apenas uma vez.
Aqui está um pequeno programa que mostra todos esses conceitos.
#include <functional> // for std::bind
#include <memory> // for std::unique_ptr
#include <utility> // for std::move
#include <future> // for std::packaged_task
#include <iostream> // printing
#include <type_traits> // for std::result_of
#include <cstddef>
void showPtr(const char* name, const std::unique_ptr<size_t>& ptr)
{
std::cout << "- &" << name << " = " << &ptr << ", " << name << ".get() = "
<< ptr.get();
if (ptr)
std::cout << ", *" << name << " = " << *ptr;
std::cout << std::endl;
}
// If you must use std::function, but your function is MoveConstructable
// but not CopyConstructable, you can wrap it in a shared pointer.
template <typename F>
class shared_function : public std::shared_ptr<F> {
public:
using std::shared_ptr<F>::shared_ptr;
template <typename ...Args>
auto operator()(Args&&...args) const
-> typename std::result_of<F(Args...)>::type
{
return (*(this->get()))(std::forward<Args>(args)...);
}
};
template <typename F>
shared_function<F> make_shared_fn(F&& f)
{
return shared_function<F>{
new typename std::remove_reference<F>::type{std::forward<F>(f)}};
}
int main()
{
std::unique_ptr<size_t> myPointer(new size_t{42});
showPtr("myPointer", myPointer);
std::cout << "Creating lambda\n";
#if __cplusplus == 201103L // C++ 11
// Use std::bind
auto lambda = std::bind([](std::unique_ptr<size_t>& myPointerArg){
showPtr("myPointerArg", myPointerArg);
*myPointerArg *= 56; // Reads our movable thing
showPtr("myPointerArg", myPointerArg);
myPointerArg.reset(new size_t{*myPointerArg * 237}); // Writes it
showPtr("myPointerArg", myPointerArg);
}, std::move(myPointer));
#elif __cplusplus > 201103L // C++14
// Use generalized capture
auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
showPtr("myPointerCapture", myPointerCapture);
*myPointerCapture *= 56;
showPtr("myPointerCapture", myPointerCapture);
myPointerCapture.reset(new size_t{*myPointerCapture * 237});
showPtr("myPointerCapture", myPointerCapture);
};
#else
#error We need C++11
#endif
showPtr("myPointer", myPointer);
std::cout << "#1: lambda()\n";
lambda();
std::cout << "#2: lambda()\n";
lambda();
std::cout << "#3: lambda()\n";
lambda();
#if ONLY_NEED_TO_CALL_ONCE
// In some situations, std::packaged_task is an alternative to
// std::function, e.g., if you only plan to call it once. Otherwise
// you need to write your own wrapper to handle move-only function.
std::cout << "Moving to std::packaged_task\n";
std::packaged_task<void()> f{std::move(lambda)};
std::cout << "#4: f()\n";
f();
#else
// Otherwise, we need to turn our move-only function into one that can
// be copied freely. There is no guarantee that it'll only be copied
// once, so we resort to using a shared pointer.
std::cout << "Moving to std::function\n";
std::function<void()> f{make_shared_fn(std::move(lambda))};
std::cout << "#4: f()\n";
f();
std::cout << "#5: f()\n";
f();
std::cout << "#6: f()\n";
f();
#endif
}
Eu coloquei um programa acima no Coliru , para que você possa executar e brincar com o código.
Aqui estão algumas saídas típicas ...
- &myPointer = 0xbfffe5c0, myPointer.get() = 0x7ae3cfd0, *myPointer = 42
Creating lambda
- &myPointer = 0xbfffe5c0, myPointer.get() = 0x0
#1: lambda()
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 42
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 2352
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 557424
#2: lambda()
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 557424
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 31215744
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 3103164032
#3: lambda()
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 3103164032
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 1978493952
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360
Moving to std::function
#4: f()
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3436650496
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608
#5: f()
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2967666688
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3257335808
#6: f()
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3257335808
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 2022178816
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2515009536
Você pode ver os locais de pilha sendo reutilizados, mostrando que o std::unique_ptr
está funcionando corretamente. Você também vê a própria função se mover quando a guardamos em um invólucro no qual alimentamos std::function
.
Se passarmos a usar std::packaged_task
, a última parte se tornará
Moving to std::packaged_task
#4: f()
- &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360
- &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3436650496
- &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608
então vemos que a função foi movida, mas, em vez de ser movida para o heap, ela está dentro do std::packaged_task
que está na pilha.
Espero que isto ajude!
Tarde, mas como algumas pessoas (inclusive eu) ainda estão presas no c ++ 11:
Para ser sincero, não gosto de nenhuma das soluções postadas. Tenho certeza de que eles funcionarão, mas exigem muitas coisas adicionais e / ou std::bind
sintaxe criptográfica ... e não acho que valha a pena o esforço para uma solução temporária que será refatorada de qualquer maneira ao atualizar para c ++> = 14. Portanto, acho que a melhor solução é evitar a captura de movimentos para o c ++ 11 completamente.
Normalmente, a solução mais simples e melhor legível é usar std::shared_ptr
, que são copiáveis e, portanto, a mudança é completamente evitável. A desvantagem é que é um pouco menos eficiente, mas em muitos casos a eficiência não é tão importante.
// myPointer could be a parameter or something
std::unique_ptr<int> myPointer(new int);
// convert/move the unique ptr into a shared ptr
std::shared_ptr<int> mySharedPointer( std::move(myPointer) );
std::function<void(void)> = [mySharedPointer](){
*mySharedPointer = 4;
};
// at end of scope the original mySharedPointer is destroyed,
// but the copy still lives in the lambda capture.
.
Se ocorrer um caso muito raro, é realmente obrigatório move
o ponteiro (por exemplo, você deseja excluir explicitamente um ponteiro em um thread separado devido à longa duração da exclusão ou o desempenho é absolutamente crucial), esse é o único caso em que ainda uso ponteiros brutos em c ++ 11. Estes também são, obviamente, copiáveis.
Normalmente, marquei esses casos raros com a //FIXME:
para garantir que ele seja refatorado após a atualização para o c ++ 14.
// myPointer could be a parameter or something
std::unique_ptr<int> myPointer(new int);
//FIXME:c++11 upgrade to new move capture on c++>=14
// "move" the pointer into a raw pointer
int* myRawPointer = myPointer.release();
// capture the raw pointer as a copy.
std::function<void(void)> = [myRawPointer](){
std::unique_ptr<int> capturedPointer(myRawPointer);
*capturedPointer = 4;
};
// ensure that the pointer's value is not accessible anymore after capturing
myRawPointer = nullptr;
Sim, os ponteiros brutos estão bastante desaprovados nos dias de hoje (e não sem razão), mas eu realmente acho que nesses casos raros (e temporários!) Eles são a melhor solução.
Eu estava olhando para essas respostas, mas achei difícil de ler e entender. Então, o que eu fiz foi criar uma classe que seguiu em frente. Dessa forma, é explícito o que está fazendo.
#include <iostream>
#include <memory>
#include <utility>
#include <type_traits>
#include <functional>
namespace detail
{
enum selection_enabler { enabled };
}
#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), ::detail::selection_enabler> \
= ::detail::enabled
// This allows forwarding an object using the copy constructor
template <typename T>
struct move_with_copy_ctor
{
// forwarding constructor
template <typename T2
// Disable constructor for it's own type, since it would
// conflict with the copy constructor.
, ENABLE_IF(
!std::is_same<std::remove_reference_t<T2>, move_with_copy_ctor>::value
)
>
move_with_copy_ctor(T2&& object)
: wrapped_object(std::forward<T2>(object))
{
}
// move object to wrapped_object
move_with_copy_ctor(T&& object)
: wrapped_object(std::move(object))
{
}
// Copy constructor being used as move constructor.
move_with_copy_ctor(move_with_copy_ctor const& object)
{
std::swap(wrapped_object, const_cast<move_with_copy_ctor&>(object).wrapped_object);
}
// access to wrapped object
T& operator()() { return wrapped_object; }
private:
T wrapped_object;
};
template <typename T>
move_with_copy_ctor<T> make_movable(T&& object)
{
return{ std::forward<T>(object) };
}
auto fn1()
{
std::unique_ptr<int, std::function<void(int*)>> x(new int(1)
, [](int * x)
{
std::cout << "Destroying " << x << std::endl;
delete x;
});
return [y = make_movable(std::move(x))]() mutable {
std::cout << "value: " << *y() << std::endl;
return;
};
}
int main()
{
{
auto x = fn1();
x();
std::cout << "object still not deleted\n";
x();
}
std::cout << "object was deleted\n";
}
A move_with_copy_ctor
classe e sua função auxiliar funcionarão make_movable()
com qualquer objeto móvel, mas não copiável. Para obter acesso ao objeto agrupado, use o operator()()
.
Saída esperada:
valor: 1 objeto ainda não foi excluído valor: 1 Destruindo 000000DFDD172280 objeto foi excluído
Bem, o endereço do ponteiro pode variar. ;)