Captura lambda generalizada em C ++ 14
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.
Uma solução alternativa para captura de movimentação
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.
Emulando captura lambda generalizada em C ++ 11
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: PVocê também pode usar
std::bind
para capturar ounique_ptr
:fonte
unique_ptr
referência rvalue não pode ser ligada a umint *
.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: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:
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 umstd::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á usarstd::packaged_task
; faria o mesmo trabalho questd::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.
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 ...
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 alimentamosstd::function
.Se passarmos a usar
std::packaged_task
, a última parte se tornará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!
fonte
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..
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.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.
fonte
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.
A
move_with_copy_ctor
classe e sua função auxiliar funcionarãomake_movable()
com qualquer objeto móvel, mas não copiável. Para obter acesso ao objeto agrupado, use ooperator()()
.Saída esperada:
Bem, o endereço do ponteiro pode variar. ;)
Demo
fonte
Isso parece funcionar no gcc4.8
fonte