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::vectorpreencheremos 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_ptrainda é 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 ifstreamnã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 reserveespaço vectorpara que tenhamos certeza de que nossos ifstreams 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 ifstreamobjetos em um vectordiretamente, 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_ptrapenas 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.