C++ Cópia e Movimento
Quando você escreve int b = a;, o C++ copia um número. Fácil. Mas o que acontece em Caderno b = a;, quando a é um objeto de uma classe sua? Quem faz a cópia? Copia o quê? Nesta aula você vai ver as duas funções que o C++ usa para copiar objetos, descobrir quando elas rodam (às vezes em lugares inesperados), conhecer o bug clássico da "cópia rasa" que derruba o programa e aprender as regras que os programadores C++ seguem para nunca cair nele.
- O construtor de cópia
Classe(const Classe&)e a atribuição de cópiaoperator=. - Em que linhas cada um é chamado:
Classe b = a;,b = a;, passagem por valor,push_back. - A diferença entre cópia rasa e cópia profunda, e o erro double free que a cópia rasa causa.
- A regra dos três, a regra dos cinco e a melhor de todas, a regra do zero.
- Como proibir cópias com
= delete, e o erro que o compilador mostra. - Onde entra o movimento (a aula completa é Semântica de Movimento).
Esta aula usa o que você viu em Construtores, Destrutores e RAII e Referências. Os programas foram compilados com o g++ 15 na máquina da Ana, na pasta ~/cpp, com g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.
A ideia: xerox ou caderno compartilhado?
A Ana empresta o caderno de receitas para a Bia. Há dois jeitos:
- Tirar uma xerox de cada página. A Bia pode rabiscar a cópia dela à vontade: o caderno da Ana continua igual.
- Dar um bilhete dizendo "o caderno está na minha estante". Agora as duas usam o mesmo caderno: o que a Bia rabiscar, a Ana vai ver. E se as duas resolverem jogar "o seu" caderno fora, alguém vai tentar jogar fora um caderno que não existe mais.
Em C++, a xerox é a cópia profunda; o bilhete é a cópia rasa. Para tipos como int, std::string e std::vector, o C++ já faz a xerox sozinho. O perigo aparece quando a classe guarda um ponteiro cru (int*), que é exatamente um "bilhete" dizendo onde está a coisa.
1. As duas funções de cópia
Toda classe tem duas funções responsáveis por copiar. Se você não escrever, o compilador cria as duas, e elas copiam atributo por atributo.
| Função | Forma | Quando roda |
|---|---|---|
| Construtor de cópia | Classe(const Classe& outro) | Quando um objeto novo nasce como cópia de outro. |
| Atribuição de cópia | Classe& operator=(const Classe& outro) | Quando um objeto que já existe recebe o valor de outro. |
Para ver quando cada uma roda, vamos escrevê-las nós mesmos, com um std::cout dentro:
#include <iostream>
#include <string>
class Caderno {
public:
Caderno(std::string dono) : dono_{dono} {
std::cout << " [construtor] " << dono_ << '\n';
}
Caderno(const Caderno& outro) : dono_{outro.dono_} {
std::cout << " [cópia] copiei o caderno de " << dono_ << '\n';
}
Caderno& operator=(const Caderno& outro) {
std::cout << " [atribuição] " << dono_ << " recebe o de " << outro.dono_ << '\n';
dono_ = outro.dono_;
return *this;
}
~Caderno() {
std::cout << " [destrutor] " << dono_ << '\n';
}
std::string dono() const { return dono_; }
private:
std::string dono_;
};
void por_valor(Caderno c) {
std::cout << " dentro de por_valor: " << c.dono() << '\n';
}
void por_referencia(const Caderno& c) {
std::cout << " dentro de por_referencia: " << c.dono() << '\n';
}
int main() {
std::cout << "1) criar a\n";
Caderno a{"Ana"};
std::cout << "2) Caderno b = a;\n";
Caderno b = a;
std::cout << "3) Caderno c{a};\n";
Caderno c{a};
std::cout << "4) Caderno d{\"Bia\"}; d = a;\n";
Caderno d{"Bia"};
d = a;
std::cout << "5) por_valor(a)\n";
por_valor(a);
std::cout << "6) por_referencia(a)\n";
por_referencia(a);
std::cout << "7) fim do main\n";
}
- Caderno(const Caderno& outro)
- O construtor de cópia. Recebe o objeto original por referência constante:
¶ não copiar (copiar para poder copiar seria um círculo sem fim) econstporque copiar não deve mudar o original. - : dono_{outro.dono_}
- Inicializa o atributo do objeto novo com o do original. Uma classe pode ler os atributos privados de outro objeto da mesma classe.
- Caderno& operator=(const Caderno& outro)
- A atribuição de cópia: é a função que roda no sinal
=entre dois objetos que já existem. O nomeoperator=é a forma de "ensinar" um operador a uma classe (aula Sobrecarga de Operadores). - return *this;
- Devolve o próprio objeto (this), para permitir
x = y = z;, como com números.
$ g++ -std=c++23 -Wall -Wextra copia.cpp -o copia
$ ./copia
1) criar a
[construtor] Ana
2) Caderno b = a;
[cópia] copiei o caderno de Ana « tem um "=", mas é construtor de cópia: b está nascendo
3) Caderno c{a};
[cópia] copiei o caderno de Ana
4) Caderno d{"Bia"}; d = a;
[construtor] Bia
[atribuição] Bia recebe o de Ana « d já existia: atribuição
5) por_valor(a)
[cópia] copiei o caderno de Ana « o parâmetro c é uma cópia nova
dentro de por_valor: Ana
[destrutor] Ana « e morre no fim da função
6) por_referencia(a)
dentro de por_referencia: Ana « nenhuma cópia!
7) fim do main
[destrutor] Ana
[destrutor] Ana
[destrutor] Ana
[destrutor] Ana
A pegadinha está no caso 2: Caderno b = a; tem um sinal de igual, mas não chama a atribuição. Como b está nascendo nessa linha, quem roda é o construtor de cópia. A regra para não errar:
- O objeto da esquerda está nascendo nesta linha (tem o tipo na frente)? Construtor de cópia.
- O objeto da esquerda já existia? Atribuição de cópia (
operator=).
Os casos 5 e 6 mostram por que, nas aulas de funções, o guia recomenda const Classe& para objetos (Por Valor × Por Referência): passar por valor fez uma cópia inteira (e uma destruição) só para ler o nome.
Mais dois lugares: return e push_back
Caderno criar() {
Caderno novo{"Caio"};
return novo;
}
int main() {
std::cout << "1) Caderno x = criar();\n";
Caderno x = criar();
std::cout << "2) vector + push_back(x)\n";
std::vector<Caderno> pilha;
pilha.push_back(x);
std::cout << "3) fim do main\n";
}
$ g++ -std=c++23 -Wall -Wextra retorno.cpp -o retorno $ ./retorno 1) Caderno x = criar(); [construtor] Caio « nenhuma cópia no return! 2) vector + push_back(x) [cópia] Caio « o vetor guarda uma cópia de x 3) fim do main [destrutor] Caio [destrutor] Caio
Devolver um objeto de uma função não copiou nada: o compilador construiu o novo direto no lugar de x. Isso se chama elisão de cópia (copy elision, "omitir a cópia"), e é por isso que devolver objetos por valor é barato em C++. Já o push_back guardou uma cópia de x: o vetor tem o seu próprio caderno.
Experimente: rode e confira cada linha. Depois troque por_valor(Caderno c) por por_valor(const Caderno& c) e veja a cópia e a destruição desaparecerem. Por último, acrescente a = a; no fim do main: quem roda?
#include <iostream>
#include <string>
class Caderno {
public:
Caderno(std::string dono) : dono_{dono} {
std::cout << " [construtor] " << dono_ << '\n';
}
Caderno(const Caderno& outro) : dono_{outro.dono_} {
std::cout << " [cópia] " << dono_ << '\n';
}
Caderno& operator=(const Caderno& outro) {
std::cout << " [atribuição] " << dono_ << " recebe o de " << outro.dono_ << '\n';
dono_ = outro.dono_;
return *this;
}
~Caderno() {
std::cout << " [destrutor] " << dono_ << '\n';
}
std::string dono() const { return dono_; }
private:
std::string dono_;
};
void por_valor(Caderno c) {
std::cout << " lendo: " << c.dono() << '\n';
}
int main() {
Caderno a{"Ana"};
Caderno b = a;
Caderno d{"Bia"};
d = a;
por_valor(a);
std::cout << "fim do main\n";
}
2. Cópia rasa × cópia profunda
A cópia que o compilador gera copia atributo por atributo. Para um std::string, "copiar o atributo" chama a cópia da própria string, que faz uma xerox do texto. Tudo certo. Mas, para um ponteiro cru, "copiar o atributo" copia só o endereço: o bilhete, não o caderno. Veja uma classe que guarda notas num array criado com new[]:
#include <iostream>
class Notas {
public:
Notas(int tamanho) : tamanho_{tamanho}, dados_{new int[tamanho]{}} {}
~Notas() { delete[] dados_; }
int tamanho() const { return tamanho_; }
void definir(int i, int valor) { dados_[i] = valor; }
int pegar(int i) const { return dados_[i]; }
private:
int tamanho_;
int* dados_;
};
int main() {
Notas ana{3};
ana.definir(0, 7);
Notas bia = ana; // cópia feita pelo compilador
bia.definir(0, 10); // mudei só a Bia...
std::cout << "Ana: " << ana.pegar(0) << '\n';
std::cout << "Bia: " << bia.pegar(0) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra rasa.cpp -o rasa $ ./rasa Ana: 10 « a nota da Ana mudou sozinha! Bia: 10 free(): double free detected in tcache 2 Abortado (imagem do núcleo gravada) ./rasa $ echo $? 134
Dois problemas, nenhum aviso do compilador:
- Estado compartilhado.
bia.dados_recebeu o mesmo endereço deana.dados_. As duas apontam para o mesmo array; mudar a Bia mudou a Ana. - Double free ("liberação dupla"). No fim do
main,biamorre e fazdelete[]no array. Depoisanamorre e fazdelete[]no mesmo array, que já tinha sido devolvido. A biblioteca C percebeu (double free detected) e abortou o programa.
Com o AddressSanitizer (-fsanitize=address), o diagnóstico fica bem mais claro. A saída é longa; aqui vão as partes que importam:
$ g++ -std=c++23 -Wall -Wextra -g -fsanitize=address rasa.cpp -o rasa
$ ./rasa
Ana: 10
Bia: 10
=================================================================
==526964==ERROR: AddressSanitizer: attempting double-free on 0x799f015e0010 in thread T0:
#0 0x7d7f02b2d46f in operator delete[](void*) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:179
#1 0x608446baf7eb in Notas::~Notas() /home/ana/cpp/rasa.cpp:6
#2 0x608446baf578 in main /home/ana/cpp/rasa.cpp:25
« (linhas internas da biblioteca omitidas)
freed by thread T0 here:
#0 0x7d7f02b2d46f in operator delete[](void*) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:179
#1 0x608446baf7eb in Notas::~Notas() /home/ana/cpp/rasa.cpp:6
#2 0x608446baf56c in main /home/ana/cpp/rasa.cpp:25
« (…)
previously allocated by thread T0 here:
#0 0x7d7f02b2c8cf in operator new[](unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:111
#1 0x608446baf6ec in Notas::Notas(int) /home/ana/cpp/rasa.cpp:5
#2 0x608446baf458 in main /home/ana/cpp/rasa.cpp:17
« (…)
SUMMARY: AddressSanitizer: double-free /home/ana/cpp/rasa.cpp:6 in Notas::~Notas()
==526964==ABORTING
Leia em três blocos: "attempting double-free" (tentativa de liberar duas vezes) no destrutor, linha 6; "freed by … here" (já tinha sido liberado aqui), no mesmo destrutor, chamado para o outro objeto; e "previously allocated" (alocado antes aqui), no construtor, linha 5, quando a Ana nasceu na linha 17. Só um array foi criado, e dois objetos tentaram devolvê-lo.
O conserto à moda antiga: cópia profunda
Para a cópia virar uma xerox, você escreve as duas funções de cópia à mão. Cada uma cria um array novo e copia os valores, um por um:
class Notas {
public:
Notas(int tamanho) : tamanho_{tamanho}, dados_{new int[tamanho]{}} {}
~Notas() { delete[] dados_; }
// construtor de cópia: memória NOVA, com os mesmos valores
Notas(const Notas& outra)
: tamanho_{outra.tamanho_}, dados_{new int[outra.tamanho_]} {
for (int i = 0; i < tamanho_; ++i) {
dados_[i] = outra.dados_[i];
}
}
// atribuição de cópia: joga fora o antigo e copia o novo
Notas& operator=(const Notas& outra) {
if (this == &outra) return *this; // a = a; não faz nada
int* novos = new int[outra.tamanho_];
for (int i = 0; i < outra.tamanho_; ++i) {
novos[i] = outra.dados_[i];
}
delete[] dados_;
dados_ = novos;
tamanho_ = outra.tamanho_;
return *this;
}
// ... tamanho, definir e pegar como antes
private:
int tamanho_;
int* dados_;
};
| Trecho | Por quê |
|---|---|
dados_{new int[outra.tamanho_]} | O objeto novo ganha o seu próprio array. |
o for | Copia os valores, um por um: a xerox. |
if (this == &outra) | Protege contra ana = ana;. Sem isso, o objeto apagaria os próprios dados antes de copiá-los. |
new antes do delete[] | Se o new falhar (falta de memória), o objeto continua com os dados antigos, inteiro. |
$ g++ -std=c++23 -Wall -Wextra -g -fsanitize=address profunda.cpp -o profunda $ ./profunda Ana: 7 « a Ana não foi afetada Bia: 10 Caio: 5 (tamanho 3) « caio = ana; depois caio.definir(0, 5);
Funciona, e o AddressSanitizer ficou quieto. Mas veja quanto código foi preciso para um array de inteiros, e quantos lugares para errar. Existe um jeito muito melhor.
3. Regra dos três, dos cinco e do zero
Regra dos três
Se a sua classe precisou de um destes três, quase certamente precisa dos três:
- destrutor (
~Classe()); - construtor de cópia (
Classe(const Classe&)); - atribuição de cópia (
operator=(const Classe&)).
O motivo: se você escreveu um destrutor, é porque a classe cuida de um recurso "na mão" (o delete[]), e a cópia automática não sabe duplicar esse recurso. Foi exatamente o caso da Notas.
Regra dos cinco C++11
O C++11 acrescentou duas funções de movimento: o construtor de movimento Classe(Classe&&) e a atribuição de movimento operator=(Classe&&). Elas "roubam" o recurso de um objeto que vai morrer, em vez de copiá-lo, o que é muito mais rápido. A regra dos três virou a regra dos cinco: quem escreve uma das cinco deve pensar nas cinco. O movimento tem aula própria: Semântica de Movimento.
Regra do zero: a melhor
A recomendação moderna é não escrever nenhuma das cinco. Em vez de guardar um ponteiro cru e cuidar dele à mão, guarde um tipo que já sabe se copiar, mover e destruir (RAII, da aula anterior): std::vector, std::string, std::unique_ptr. A cópia automática, atributo por atributo, passa a fazer a coisa certa:
#include <iostream>
#include <vector>
class Notas {
public:
Notas(int tamanho) : dados_(tamanho) {}
int tamanho() const { return static_cast<int>(dados_.size()); }
void definir(int i, int valor) { dados_[i] = valor; }
int pegar(int i) const { return dados_[i]; }
private:
std::vector<int> dados_;
};
int main() {
Notas ana{3};
ana.definir(0, 7);
Notas bia = ana;
bia.definir(0, 10);
std::cout << "Ana: " << ana.pegar(0) << '\n';
std::cout << "Bia: " << bia.pegar(0) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra zero.cpp -o zero $ ./zero Ana: 7 Bia: 10
Sem destrutor, sem construtor de cópia, sem operator=, sem new: a classe ficou menor e correta. Repare no dados_(tamanho) com parênteses: para std::vector, (3) cria três elementos valendo zero, enquanto {3} criaria um elemento só, valendo 3 (detalhe da aula std::vector).
| Regra | Quando usar |
|---|---|
| Zero | Quase sempre. Atributos que já se cuidam (string, vector, unique_ptr…) e nenhuma das cinco funções escrita à mão. |
| Cinco | Quando você escreve uma classe que é o dono de um recurso cru (como a Arquivo da aula anterior). Escreva as cinco, ou proíba as que não fazem sentido. |
| Três | O mesmo que a dos cinco, em código anterior ao C++11. |
4. Proibir a cópia com = delete
Alguns objetos não deveriam ser copiados de jeito nenhum: um arquivo aberto, uma conexão com o banco, uma senha guardada na memória. Para proibir, declare as funções de cópia e escreva = delete no lugar do corpo C++11:
#include <iostream>
#include <string>
class Senha {
public:
Senha(std::string texto) : texto_{texto} {}
Senha(const Senha&) = delete; // proíbe: Senha b = a;
Senha& operator=(const Senha&) = delete; // proíbe: b = a;
bool confere(const std::string& tentativa) const { return tentativa == texto_; }
private:
std::string texto_;
};
int main() {
Senha minha{"gato123"};
Senha copia = minha;
std::cout << copia.confere("gato123") << '\n';
}
$ g++ -std=c++23 -Wall -Wextra proibido.cpp -o proibido
proibido.cpp: In function ‘int main()’:
proibido.cpp:18:19: error: use of deleted function ‘Senha::Senha(const Senha&)’
18 | Senha copia = minha;
| ^~~~~
proibido.cpp:8:5: note: declared here
8 | Senha(const Senha&) = delete; // proíbe: Senha b = a;
| ^~~~~
proibido.cpp:18:19: note: use ‘-fdiagnostics-all-candidates’ to display considered candidates
18 | Senha copia = minha;
| ^~~~~
- error: use of deleted function
- "uso de função apagada": a linha 18 tentou usar uma função marcada com
= delete. - ‘Senha::Senha(const Senha&)’
- Qual função: o construtor de cópia da
Senha. - note: declared here
- Onde ela foi apagada (linha 8). É o compilador mostrando que a proibição foi de propósito.
- note: use ‘-fdiagnostics-all-candidates’
- Uma dica: com essa opção, o g++ lista todas as funções que considerou. Pode ignorar.
O erro aparece na compilação, antes de o programa existir: muito melhor que um double free em produção. O Clang do editor diz o mesmo com outras palavras: call to deleted constructor of 'Senha'. Proibir a cópia também proíbe passar uma Senha por valor; use const Senha&.
= default: o contrário
Se você quer deixar claro que a cópia automática está boa, escreva Classe(const Classe&) = default;: "use a versão que o compilador gera". Pela regra do zero, normalmente nem isso é preciso.
Quando dá errado
$ ./rasa Ana: 10 Bia: 10 free(): double free detected in tcache 2 Abortado (imagem do núcleo gravada) ./rasa
O que significa: a mesma memória foi liberada duas vezes. Quase sempre é uma classe com ponteiro cru e destrutor, mas com a cópia automática (rasa). tcache é um detalhe interno da biblioteca C; não ajuda a achar o erro. O código de saída é 134 (abortado).
Como consertar: regra do zero (troque o ponteiro cru por std::vector, std::string ou std::unique_ptr), ou escreva a cópia profunda, ou proíba a cópia com = delete. Para achar onde, compile com -g -fsanitize=address.
O que significa: é a outra cara da cópia rasa: os dois objetos dividem o mesmo recurso por um ponteiro. Pode acontecer sem nenhum erro visível, se a classe não tiver destrutor (e aí ela vaza memória).
Como consertar: o mesmo do cenário anterior.
O que significa: a classe proíbe cópias (como Senha, std::unique_ptr ou std::ofstream), e a função recebe o parâmetro por valor, o que exigiria uma cópia.
Como consertar: receba por referência (const Senha&). Se a intenção é entregar o objeto para a função, use std::move (aula Semântica de Movimento).
operator= apagou os próprios dados
O que significa: um operator= escrito à mão que faz delete[] dados_ antes de copiar quebra em a = a;: ele apaga o array e depois tenta copiar do array apagado. Isso também acontece de forma escondida, como em lista[i] = lista[j]; quando i == j.
Como consertar: teste if (this == &outra) return *this; e crie o array novo antes de apagar o antigo. Ou, melhor, regra do zero.
Missões
Missão 1 (regra do zero): esta Notas usa um ponteiro cru, e a cópia da Bia muda a nota da Ana. Troque o int* por um std::vector<int> (inclua <vector>), apague o destrutor e ajuste o construtor, até a saída ser Ana: 7 e Bia: 10.
#include <iostream>
class Notas {
public:
Notas(int tamanho) : dados_{new int[tamanho]{}} {}
~Notas() { delete[] dados_; }
void definir(int i, int valor) { dados_[i] = valor; }
int pegar(int i) const { return dados_[i]; }
private:
int* dados_;
};
int main() {
Notas ana{3};
ana.definir(0, 7);
Notas bia = ana;
bia.definir(0, 10);
std::cout << "Ana: " << ana.pegar(0) << '\n';
std::cout << "Bia: " << bia.pegar(0) << '\n';
}
#include <iostream>
#include <vector>
class Notas {
public:
Notas(int tamanho) : dados_(tamanho) {}
void definir(int i, int valor) { dados_[i] = valor; }
int pegar(int i) const { return dados_[i]; }
private:
std::vector<int> dados_;
};
int main() {
Notas ana{3};
ana.definir(0, 7);
Notas bia = ana;
bia.definir(0, 10);
std::cout << "Ana: " << ana.pegar(0) << '\n';
std::cout << "Bia: " << bia.pegar(0) << '\n';
}
Missão 2 (provoque o erro): uma Chave do cofre não pode ter cópias. Acrescente na classe as duas linhas com = delete que proíbem copiar e rode: a compilação deve falhar na linha Chave reserva = chave;, com um erro que fala de função deleted.
#include <iostream>
#include <string>
class Chave {
public:
Chave(std::string codigo) : codigo_{codigo} {}
// proíba a cópia aqui
std::string codigo() const { return codigo_; }
private:
std::string codigo_;
};
int main() {
Chave chave{"cofre-42"};
Chave reserva = chave;
std::cout << reserva.codigo() << '\n';
}
#include <iostream>
#include <string>
class Chave {
public:
Chave(std::string codigo) : codigo_{codigo} {}
Chave(const Chave&) = delete;
Chave& operator=(const Chave&) = delete;
std::string codigo() const { return codigo_; }
private:
std::string codigo_;
};
int main() {
Chave chave{"cofre-42"};
Chave reserva = chave;
std::cout << reserva.codigo() << '\n';
}
Referência
| Código | O que roda |
|---|---|
Classe b = a; ou Classe b{a}; | Construtor de cópia |
b = a; (b já existe) | Atribuição de cópia, operator= |
f(a) com void f(Classe x) | Construtor de cópia (para o parâmetro) |
f(a) com void f(const Classe& x) | Nada: é uma referência |
return obj; de um objeto local | Normalmente nada (elisão de cópia); senão, movimento |
v.push_back(a); | Construtor de cópia (o vetor guarda a sua própria cópia) |
Classe(const Classe&) = delete; | Proíbe a cópia: erro de compilação em quem tentar |
Classe(const Classe&) = default; | Pede a cópia automática, atributo por atributo |
Exercícios
1. A assinatura
Complete o construtor de cópia da classe Produto:
2. Proibir
Complete para proibir a atribuição de cópia da classe Conexao:
3. Quantas cópias?
Com a classe Caderno da seção 1 (que imprime [cópia]), quantas linhas [cópia] aparecem em: Caderno a{"Ana"}; Caderno b = a; Caderno c{"Caio"}; c = b;?
4. Uma classe guarda int* dados_ criado com new[] e tem destrutor com delete[], mas não tem construtor de cópia. O que acontece em Classe b = a; e no fim do programa?
5. O que diz a regra do zero?
6. Em qual linha roda a atribuição de cópia (e não o construtor de cópia)?
Resumo
- O construtor de cópia
Classe(const Classe&)roda quando um objeto nasce como cópia (inclusive emClasse b = a;, passagem por valor epush_back). - A atribuição de cópia
operator=roda quando um objeto que já existe recebe outro. - A cópia automática copia atributo por atributo. Com ponteiro cru, isso é uma cópia rasa: estado compartilhado e double free.
- Cópia profunda = memória nova + copiar os valores. Funciona, mas dá trabalho.
- Regra dos três/cinco: escreveu uma das funções especiais, pense em todas. Regra do zero: prefira atributos RAII e não escreva nenhuma.
= deleteproíbe a cópia com erro de compilação (use of deleted function).- Devolver objeto por valor costuma não copiar nada (elisão de cópia). O movimento, a outra metade desta história, está em Semântica de Movimento.