A semântica de movimento não é necessariamente uma melhoria tão grande quando você retorna um valor - e quando / se você usa um shared_ptr
(ou algo semelhante) provavelmente está pessimizando prematuramente. Na realidade, quase todos os compiladores razoavelmente modernos fazem o que chamamos de Return Value Optimization (RVO) e Node Return Value Optimization (NRVO). Isto significa que quando você está retornando um valor, em vez de realmente copiar o valor em tudo, eles simplesmente passam um ponteiro / referência oculta para onde o valor será atribuído após o retorno, e a função usa isso para criar o valor para o final. O padrão C ++ inclui disposições especiais para permitir isso, portanto, mesmo que (por exemplo) seu construtor de cópias tenha efeitos colaterais visíveis, não é necessário usar o construtor de cópias para retornar o valor. Por exemplo:
#include <vector>
#include <numeric>
#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <iterator>
class X {
std::vector<int> a;
public:
X() {
std::generate_n(std::back_inserter(a), 32767, ::rand);
}
X(X const &x) {
a = x.a;
std::cout << "Copy ctor invoked\n";
}
int sum() { return std::accumulate(a.begin(), a.end(), 0); }
};
X func() {
return X();
}
int main() {
X x = func();
std::cout << "sum = " << x.sum();
return 0;
};
A idéia básica aqui é bastante simples: crie uma classe com conteúdo suficiente, e preferimos evitar copiá-la, se possível ( std::vector
preencheremos com 32767 ints aleatórios). Temos um copiador explícito que nos mostrará quando / se for copiado. Também temos um pouco mais de código para fazer algo com os valores aleatórios no objeto, para que o otimizador não elimine (pelo menos facilmente) tudo sobre a classe apenas porque ela não faz nada.
Temos então algum código para retornar um desses objetos de uma função e, em seguida, usamos a soma para garantir que o objeto seja realmente criado, e não apenas ignorado completamente. Quando executamos, pelo menos com os compiladores mais recentes / modernos, descobrimos que o construtor de cópias que escrevemos nunca é executado - e sim, tenho certeza de que mesmo uma cópia rápida com a shared_ptr
ainda é mais lenta do que não copiar. em absoluto.
Mover permite que você faça um bom número de coisas que você simplesmente não poderia fazer (diretamente) sem elas. Considere a parte "mesclar" de uma classificação de mesclagem externa - você tem, digamos, 8 arquivos que serão mesclados. Idealmente, você gostaria de colocar todos os 8 desses arquivos em um vector
- mas como vector
(a partir do C ++ 03) precisa copiar elementos e ifstream
não pode ser copiado, você está preso a alguns unique_ptr
/ shared_ptr
, ou algo nessa ordem para poder colocá-los em um vetor. Observe que, mesmo que (por exemplo) espaçemos o reserve
espaço vector
para que tenhamos certeza de que nossos ifstream
s nunca serão realmente copiados, o compilador não saberá disso, portanto o código não será compilado, mesmo sabendo que o construtor de cópias nunca será usado de qualquer maneira.
Mesmo que ainda não possa ser copiado, no C ++ 11 um ifstream
pode ser movido. Neste caso, os objetos provavelmente não vai nunca ser movido, mas o fato de que eles poderiam ser, se necessário, mantém o feliz compilador, para que possamos colocar nossos ifstream
objetos em um vector
diretamente, sem qualquer hacks ponteiro inteligente.
Um vetor que se expande é um exemplo bastante decente de um tempo em que a semântica de movimento realmente pode ser / é útil. Nesse caso, o RVO / NRVO não ajudará, porque não estamos lidando com o valor de retorno de uma função (ou qualquer coisa muito semelhante). Temos um vetor que contém alguns objetos e queremos movê-los para um novo pedaço maior de memória.
No C ++ 03, isso foi feito criando cópias dos objetos na nova memória e destruindo os objetos antigos na memória antiga. Fazer todas essas cópias apenas para jogar fora as antigas, no entanto, era uma perda de tempo. No C ++ 11, você pode esperar que eles sejam movidos. Isso normalmente nos permite, em essência, fazer uma cópia superficial em vez de uma cópia profunda (geralmente muito mais lenta). Em outras palavras, com uma string ou vetor (por apenas alguns exemplos), apenas copiamos o (s) ponteiro (s) nos objetos, em vez de fazer cópias de todos os dados a que esses ponteiros se referem.
shared_ptr
apenas por uma cópia rápida) e se a semântica de movimento pode conseguir o mesmo com quase nenhuma penalidade de codificação, semântica e limpeza.