Middle+
O que é semântica de movimentação e como usá-la?
sobes.tech IA
Resposta da IA
A semântica de movimento (move semantics) em C++11 e versões superiores permite transferir de forma eficiente a propriedade de um recurso de um objeto para outro, evitando cópias desnecessárias. Isto é conseguido através do uso de referências rvalue (&&) e do operador de movimento (std::move).
Aspectos-chave:
- Referências rvalue (
&&): Referências a objetos temporários (ou seja, rvalues) ou a objetos marcados como movíveis. Permitem ligar-se a um objeto que será destruído em breve. - Operador de movimento (
std::move): Converte um lvalue numa referência rvalue. Por si só, não realiza o movimento, mas indica ao compilador que o objeto pode ser movido com segurança. - Construtor de movimento: Construtor específico que aceita uma referência rvalue como argumento. "Rouba" recursos (por exemplo, ponteiros para memória dinâmica) do objeto original, deixando-o num estado válido mas esvaziado.
- Operador de atribuição por movimento: Semelhante ao construtor de movimento, destinado a operações de atribuição.
Aplicações:
- Otimização: Reduz os custos associados à cópia de objetos grandes ou que possuem recursos (por exemplo, contêineres, cadeias, ponteiros inteligentes).
- Implementação de contêineres eficientes: Permite a contêineres como
std::vectorgerenciar eficazmente os elementos ao alterar o tamanho ou mover-los. - Devolver objetos grandes por valor: O compilador pode aplicar automaticamente a otimização de movimento (return value optimization, RVO, ou Named Return Values Optimization, NRVO), se o objeto devolvido for uma variável local ou um objeto temporário.
Exemplo:
#include <iostream>
#include <vector>
#include <string>
#include <utility> // Para std::move
class MyResource {
public:
std::vector<int> data;
// Construtor
MyResource(size_t size) : data(size) {
std::cout << "Construtor" << std::endl;
}
// Destrutor
~MyResource() {
std::cout << "Destrutor" << std::endl;
}
// Construtor de cópia
MyResource(const MyResource& other) : data(other.data) {
std::cout << "Construtor de cópia" << std::endl;
}
// Operador de atribuição de cópia
MyResource& operator=(const MyResource& other) {
if (this != &other) {
data = other.data;
}
std::cout << "Operador de atribuição de cópia" << std::endl;
return *this;
}
// Construtor de movimento
MyResource(MyResource&& other) noexcept : data(std::move(other.data)) {
std::cout << "Construtor de movimento" << std::endl;
// Após o movimento, o objeto original está num estado válido mas esvaziado
// other.data permanece válido, mas seu conteúdo pode ter sido movido
}
// Operador de atribuição por movimento
MyResource& operator=(MyResource&& other) noexcept {
if (this != &other) {
data = std::move(other.data);
}
std::cout << "Operador de atribuição por movimento" << std::endl;
return *this;
}
};
int main() {
MyResource res1(10); // Construtor
std::cout << "---" << std::endl;
MyResource res2 = res1; // Construtor de cópia (ou operador de atribuição de cópia, se res2 já existir)
std::cout << "---" << std::endl;
MyResource res3 = std::move(res1); // Construtor de movimento
std::cout << "---" << std::endl;
MyResource res4(5); // Construtor
res4 = std::move(res3); // Operador de atribuição por movimento
std::cout << "---" << std::endl;
return 0;
}
Neste exemplo, é possível ver que ao usar std::move, são chamados o construtor de movimento ou o operador de atribuição por movimento em vez de seus equivalentes de cópia, o que evita cópias dispendiosas do conteúdo do vetor data.