C++ this
Desde a aula Métodos, você escreve nome dentro de um método e o C++ sabe que é o nome da caneta quando a caneta chamou, e o nome do caderno quando o caderno chamou. Como ele sabe? Por um parâmetro escondido, que todo método recebe sem você ver: o ponteiro this ("este"). Nesta aula você vai conhecê-lo e usá-lo em duas situações do dia a dia: quando um parâmetro tem o mesmo nome de um atributo e quando você quer encadear chamadas, como caneta.repor(10).vender(3).
- O que é
thise como ver que ele aponta para o objeto que chamou o método. - Por que
preco = preco;não faz nada, e comothis->preco = preco;resolve. - As alternativas a
this->: nomes diferentes e o sublinhado (preco_). - Como retornar
*thispara encadear chamadas, e por que o retorno precisa ser uma referência (Produto&). - Como usar
thispara saber se dois objetos são o mesmo.
Para esta aula, ajuda lembrar de Ponteiros (o & que pega o endereço, o * que segue o ponteiro e a seta ->) e de Referências. Os programas foram compilados e executados na máquina fictícia da Ana, na pasta ~/cpp/this, com o g++ 15.2 e o comando padrão g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.
A ideia: "esta ficha aqui"
Quando a Ana segue a instrução "repor: some a quantidade ao estoque", ela está com uma ficha nas mãos. "O estoque" quer dizer "o estoque desta ficha aqui". Se ela precisasse apontar com o dedo, apontaria para a ficha que está segurando.
this é esse dedo: dentro de um método, ele aponta para o objeto que chamou o método. Em caneta.repor(10), this aponta para a caneta; em caderno.repor(10), para o caderno.
1. Ver o this
Um ponteiro guarda um endereço de memória. Vamos mostrar o endereço de cada objeto (com &) e, dentro do método, o valor de this:
#include <iostream>
#include <string>
class Produto {
public:
std::string nome;
void quemSouEu() {
std::cout << "Sou " << nome << " e moro em " << this << "\n";
}
};
int main() {
Produto caneta{"caneta"};
Produto caderno{"caderno"};
std::cout << "&caneta = " << &caneta << "\n";
std::cout << "&caderno = " << &caderno << "\n";
caneta.quemSouEu();
caderno.quemSouEu();
}
$ g++ -std=c++23 -Wall -Wextra endereco.cpp -o endereco $ ./endereco &caneta = 0x7ffd3f599000 &caderno = 0x7ffd3f599020 Sou caneta e moro em 0x7ffd3f599000 Sou caderno e moro em 0x7ffd3f599020
Os endereços batem: quando a caneta chamou quemSouEu(), this valia o endereço da caneta; quando o caderno chamou, o do caderno. (Os números em si mudam a cada execução e de máquina para máquina; o que importa é que são iguais dois a dois.)
Por baixo dos panos, o C++ transforma caneta.quemSouEu() em algo parecido com quemSouEu(&caneta): passa o endereço do objeto como um parâmetro invisível, chamado this. E, toda vez que você escreve só nome num método, o compilador entende this->nome: "o nome do objeto para onde this aponta".
Dentro de um método de Produto | Quer dizer |
|---|---|
this | Ponteiro (Produto*) para o objeto que chamou o método. |
this->nome | O atributo nome desse objeto. É o que nome sozinho já quer dizer. |
*this | O próprio objeto (seguindo o ponteiro com *). |
(*this).nome | Igual a this->nome, escrito do jeito longo. A seta existe justamente para evitar esses parênteses. |
Na maior parte do tempo você não escreve this: o compilador o coloca sozinho. Ele aparece quando você precisa dele explicitamente, nas próximas seções.
2. Parâmetro com o mesmo nome do atributo
É natural querer chamar o parâmetro de mudarPreco de preco. Veja o que acontece:
#include <iostream>
#include <string>
class Produto {
public:
std::string nome;
int preco = 0;
void mudarPreco(int preco) {
preco = preco;
}
};
int main() {
Produto caneta{"caneta", 3};
caneta.mudarPreco(5);
std::cout << caneta.nome << ": R$ " << caneta.preco << "\n";
}
$ g++ -std=c++23 -Wall -Wextra sombra.cpp -o sombra $ ./sombra caneta: R$ 3 « deveria ser 5!
O preço não mudou, e o g++ não avisou nada. Por quê? Dentro de mudarPreco, existem dois preco: o atributo do objeto e o parâmetro. A regra de escopo diz que o nome mais próximo ganha, e o mais próximo é o parâmetro. Ele esconde o atributo (diz-se que o parâmetro faz sombra, shadows, sobre o atributo). Então preco = preco; quer dizer "parâmetro recebe parâmetro": copia o 5 em cima dele mesmo, e o atributo fica intocado.
O Clang, compilador do editor do guia, percebe e avisa:
$ clang++ -std=c++23 -Wall -Wextra sombra.cpp -o sombra
sombra.cpp:10:15: warning: explicitly assigning value of variable of type 'int' to itself; did you mean to assign to member 'preco'? [-Wself-assign]
10 | preco = preco;
| ~~~~~ ^ ~~~~~
| this->
1 warning generated.
"Atribuindo explicitamente um valor de uma variável int a ela mesma; você quis atribuir ao membro preco?" E ele ainda desenha a correção: this-> na frente do primeiro preco.
A solução com this->
O parâmetro esconde o atributo, mas this->preco não deixa dúvida: é o preco do objeto.
#include <iostream>
#include <string>
class Produto {
public:
Produto(std::string nome, int preco) : nome(nome), preco(preco) {
}
void mudarPreco(int preco) {
if (preco >= 0) {
this->preco = preco;
}
}
void mostrar() {
std::cout << nome << ": R$ " << preco << "\n";
}
private:
std::string nome;
int preco;
};
int main() {
Produto caneta{"caneta", 3};
caneta.mudarPreco(5);
caneta.mostrar();
}
$ g++ -std=c++23 -Wall -Wextra desambiguar.cpp -o desambiguar $ ./desambiguar caneta: R$ 5
- this->preco = preco;
- À esquerda, o atributo do objeto; à direita, o parâmetro (o nome mais próximo). Agora o 5 vai para o lugar certo.
- if (preco >= 0)
- Sem
this->,precoé o parâmetro: estamos conferindo o valor novo, como queríamos. - : nome(nome), preco(preco)
- Na lista de inicialização, não precisa de
this: o nome de fora dos parênteses só pode ser um atributo, e o de dentro segue a regra normal (o parâmetro). Por issonome(nome)funciona e é muito comum.
Três costumes: qual usar
| Costume | Exemplo | Prós e contras |
|---|---|---|
Mesmo nome + this-> | void mudarPreco(int preco) { this->preco = preco; } | Nomes limpos. Mas basta esquecer o this-> uma vez para criar o bug acima, e o g++ não avisa. |
| Parâmetro com outro nome | void mudarPreco(int novo) { preco = novo; } | Sem ambiguidade, sem this. Às vezes é difícil achar um bom nome alternativo. |
| Atributo com marca | int preco_; … preco_ = preco; | Usado na aula public e private. Nunca há conflito, e o getter pode se chamar preco(). É o mais seguro. |
-Wshadow (fora do -Wall e do -Wextra) avisa sempre que um nome esconde outro. Com ela, o g++ diz no sombra.cpp: warning: declaration of ‘preco’ shadows a member of ‘Produto’ [-Wshadow]. O porém: ela avisa também no código correto com this-> e no nome(nome) da lista de inicialização. Por isso, quem liga -Wshadow costuma adotar o costume do sublinhado.Missão: o preço da caneta não está mudando. Rode e repare no aviso do Clang. Depois conserte o mudarPreco usando this->, sem trocar o nome do parâmetro, para a saída ser caneta: R$ 5.
#include <iostream>
#include <string>
class Produto {
public:
std::string nome;
int preco = 0;
void mudarPreco(int preco) {
preco = preco;
}
};
int main() {
Produto caneta{"caneta", 3};
caneta.mudarPreco(5);
std::cout << caneta.nome << ": R$ " << caneta.preco << "\n";
}
#include <iostream>
#include <string>
class Produto {
public:
std::string nome;
int preco = 0;
void mudarPreco(int preco) {
this->preco = preco;
}
};
int main() {
Produto caneta{"caneta", 3};
caneta.mudarPreco(5);
std::cout << caneta.nome << ": R$ " << caneta.preco << "\n";
}
3. Retornar *this para encadear chamadas
Às vezes você quer fazer várias coisas seguidas com o mesmo objeto. Em vez de
caneta.repor(10); caneta.vender(3); caneta.vender(2); caneta.mostrar();
dá para escrever tudo numa linha, uma chamada "pendurada" na outra: caneta.repor(10).vender(3).vender(2).mostrar();. Para isso funcionar, cada método precisa devolver o próprio objeto, para que o próximo . tenha onde se apoiar. O próprio objeto é *this:
#include <iostream>
#include <string>
class Produto {
public:
Produto(std::string nome, int estoque) : nome(nome), estoque(estoque) {
}
Produto& repor(int quantidade) {
estoque += quantidade;
return *this;
}
Produto& vender(int quantidade) {
if (quantidade <= estoque) {
estoque -= quantidade;
}
return *this;
}
void mostrar() {
std::cout << nome << ": " << estoque << " no estoque\n";
}
private:
std::string nome;
int estoque;
};
int main() {
Produto caneta{"caneta", 50};
caneta.repor(10).vender(3).vender(2).mostrar();
caneta.vender(5)
.repor(20)
.mostrar();
}
$ g++ -std=c++23 -Wall -Wextra encadear.cpp -o encadear $ ./encadear caneta: 55 no estoque caneta: 70 no estoque
- Produto& repor(int quantidade)
- O tipo de retorno é
Produto&: uma referência a um produto, isto é, "outro nome para um produto que já existe", sem cópia. - return *this;
thisé o ponteiro;*thisé o objeto apontado. O método devolve uma referência ao próprio objeto que o chamou.- caneta.repor(10).vender(3)
- Lido da esquerda para a direita:
caneta.repor(10)roda e devolve a própria caneta; nela, roda.vender(3), que devolve de novo a caneta; e assim por diante. 50 + 10 − 3 − 2 = 55. - caneta.vender(5)
.repor(20)
.mostrar(); - A mesma corrente, quebrada em linhas para ficar legível. O
;só vem no fim. 55 − 5 + 20 = 70.
Você já usa isso há tempos sem saber: std::cout << "a" << "b" funciona porque cada << devolve uma referência ao próprio std::cout, e o próximo << se apoia nele. Esse estilo de classe que se encadeia tem até nome: interface fluente.
Por que o & no retorno é essencial
Sem o &, o método devolveria uma cópia do objeto, e o resto da corrente trabalharia na cópia:
Produto repor(int quantidade) { // sem o &
estoque += quantidade;
return *this;
}
Produto vender(int quantidade) { // sem o &
if (quantidade <= estoque) {
estoque -= quantidade;
}
return *this;
}
…
int main() {
Produto caneta{"caneta", 50};
caneta.repor(10).vender(3).vender(2);
caneta.mostrar();
}
$ g++ -std=c++23 -Wall -Wextra copia.cpp -o copia $ ./copia caneta: 60 no estoque « as duas vendas sumiram
Compilou sem aviso, e o resultado está errado. O repor(10) mexeu na caneta de verdade (60), mas devolveu uma cópia. As duas vendas aconteceram nessa cópia (e na cópia da cópia), que desapareceram no fim da linha. A caneta original ficou com 60. Lembre: em C++, copiar um objeto cria outro objeto independente (aula O que é POO).
Classe& e o return é *this. As duas coisas juntas. Sem o &, compila e dá resultado errado em silêncio.4. this para comparar objetos
Como this é um endereço, dá para comparar com o endereço de outro objeto e descobrir se os dois são o mesmo objeto (não só parecidos: o mesmo, no mesmo lugar da memória). A Ana tem duas lojas e transfere canetas de uma para a outra; transferir de uma loja para ela mesma é engano:
class Produto {
public:
Produto(std::string nome, int estoque) : nome(nome), estoque(estoque) {
}
bool transferirPara(Produto& outro, int quantidade) {
if (this == &outro) {
std::cout << "Transferir para si mesmo não faz sentido.\n";
return false;
}
if (quantidade > estoque) {
return false;
}
estoque -= quantidade;
outro.estoque += quantidade;
return true;
}
void mostrar() {
std::cout << nome << ": " << estoque << "\n";
}
private:
std::string nome;
int estoque;
};
int main() {
Produto lojaCentro{"caneta (centro)", 50};
Produto lojaBairro{"caneta (bairro)", 5};
lojaCentro.transferirPara(lojaBairro, 20);
lojaCentro.transferirPara(lojaCentro, 1);
lojaCentro.mostrar();
lojaBairro.mostrar();
}
$ g++ -std=c++23 -Wall -Wextra transferir.cpp -o transferir $ ./transferir Transferir para si mesmo não faz sentido. caneta (centro): 30 caneta (bairro): 25
this == &outro compara dois endereços: "o objeto que está executando é o mesmo que veio no parâmetro?". Na segunda chamada, outro é a própria lojaCentro, e o método recusa. Essa checagem ("auto-atribuição") é clássica e você vai reencontrá-la em Cópia e Movimento.
Quando dá errado
this.nome com ponto, como em Java
#include <iostream>
#include <string>
class Produto {
public:
std::string nome;
void mostrar() {
std::cout << this.nome << "\n";
}
};
int main() {
Produto caneta{"caneta"};
caneta.mostrar();
}
$ g++ -std=c++23 -Wall -Wextra ponto.cpp -o ponto
ponto.cpp: In member function ‘void Produto::mostrar()’:
ponto.cpp:9:27: error: request for member ‘nome’ in ‘(Produto*)this’, which is of pointer type ‘Produto*’ (maybe you meant to use ‘->’ ?)
9 | std::cout << this.nome << "\n";
| ^~~~
O que aconteceu: em Java e C#, this é usado com ponto. Em C++, this é um ponteiro, e com ponteiros se usa a seta. A mensagem diz: "pedido do membro nome em this, que é do tipo ponteiro Produto* (talvez você quisesse usar ->?)". No Clang: member reference type 'Produto *' is a pointer; did you mean to use '->'?
Como resolver: this->nome (ou (*this).nome, ou simplesmente nome).
this fora de um método
#include <iostream>
#include <string>
class Produto {
public:
std::string nome;
};
void mostrar() {
std::cout << this->nome << "\n";
}
int main() {
mostrar();
}
$ g++ -std=c++23 -Wall -Wextra solto.cpp -o solto
solto.cpp: In function ‘void mostrar()’:
solto.cpp:10:18: error: invalid use of ‘this’ in non-member function
10 | std::cout << this->nome << "\n";
| ^~~~
O que aconteceu: "uso inválido de this em função que não é membro". mostrar é uma função solta: ninguém a chamou "sobre" um objeto, então não existe "este objeto". O mesmo vale para métodos static (aula Membros static), que também não têm this. No Clang: invalid use of 'this' outside of a non-static member function.
Como resolver: transforme a função em método da classe (void Produto::mostrar(), aula Métodos) ou receba o objeto por parâmetro (void mostrar(const Produto& p) e use p.nome).
preco = preco) e a corrente que se perde (retorno sem &)
Os dois piores erros desta aula não dão erro: compilam e dão resultado errado. Reveja as seções 2 e 3. Sintomas: um setter que "não funciona" (desconfie de parâmetro com o nome do atributo) e uma corrente de chamadas em que só a primeira tem efeito (desconfie do retorno sem &). Compilar também com o Clang, ou com -Wshadow, pega o primeiro.
Referência rápida
| Escrita | O que faz |
|---|---|
this | Ponteiro para o objeto que chamou o método. Só existe dentro de métodos (não static). |
this->preco | O atributo preco do objeto, mesmo que um parâmetro tenha o mesmo nome. |
*this | O próprio objeto. |
Produto& f() { …; return *this; } | Devolve o próprio objeto por referência, para encadear: obj.f().g(). |
if (this == &outro) | Confere se outro é o mesmo objeto que está executando. |
: preco(preco) | Na lista de inicialização, o mesmo nome funciona sem this. |
-Wshadow | Opção do g++ que avisa quando um nome esconde outro. |
Exercícios
1. Desambiguar
Complete para que o atributo estoque receba o parâmetro estoque:
2. Devolver o próprio objeto
Complete o fim do método, para permitir encadear chamadas:
3. O tipo de retorno
Complete o tipo de retorno para que caneta.vender(1).vender(2) venda as duas vezes na caneta original:
4. Em caderno.mostrar(), para onde aponta this dentro de mostrar?
5. No método void mudarPreco(int preco) { preco = preco; }, o que acontece com o atributo preco?
6. Por que this.nome dá erro em C++?
Missão final: o pedido encadeado
Missão: complete a classe Pedido com dois métodos encadeáveis: adicionar(int valor), que soma valor ao total, e desconto(int reais), que subtrai reais do total. Os dois devem devolver Pedido& com return *this;. Com o main pronto, a saída deve ser Pedido da Bia: R$ 10.
#include <iostream>
#include <string>
class Pedido {
public:
Pedido(std::string cliente) : cliente(cliente) {
}
// escreva adicionar e desconto aqui
void mostrar() {
std::cout << "Pedido da " << cliente << ": R$ " << total << "\n";
}
private:
std::string cliente;
int total = 0;
};
int main() {
Pedido pedido{"Bia"};
pedido.adicionar(12).adicionar(3).desconto(5).mostrar();
}
#include <iostream>
#include <string>
class Pedido {
public:
Pedido(std::string cliente) : cliente(cliente) {
}
Pedido& adicionar(int valor) {
total += valor;
return *this;
}
Pedido& desconto(int reais) {
total -= reais;
return *this;
}
void mostrar() {
std::cout << "Pedido da " << cliente << ": R$ " << total << "\n";
}
private:
std::string cliente;
int total = 0;
};
int main() {
Pedido pedido{"Bia"};
pedido.adicionar(12).adicionar(3).desconto(5).mostrar();
}
Resumo
thisé um ponteiro, passado escondido a todo método, que aponta para o objeto que chamou o método.nomesozinho, num método, quer dizerthis->nome.- Quando um parâmetro tem o nome de um atributo, ele esconde o atributo:
preco = preco;não faz nada (o Clang avisa; o g++ só com-Wshadow). Usethis->preco = preco;, outro nome no parâmetro ou o sublinhado no atributo. - Na lista de inicialização,
preco(preco)funciona semthis. - Para encadear chamadas, devolva
Classe&ereturn *this;. Sem o&, a corrente trabalha em cópias e as mudanças se perdem. this == &outrodiz se dois nomes são o mesmo objeto.- Em C++,
this->, nuncathis.; e não existethisfora de métodos. - Próxima aula: marcar quais métodos só leem o objeto, em Métodos const.