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).

Você vai aprender
  • O que é this e como ver que ele aponta para o objeto que chamou o método.
  • Por que preco = preco; não faz nada, e como this->preco = preco; resolve.
  • As alternativas a this->: nomes diferentes e o sublinhado (preco_).
  • Como retornar *this para encadear chamadas, e por que o retorno precisa ser uma referência (Produto&).
  • Como usar this para 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 ProdutoQuer dizer
thisPonteiro (Produto*) para o objeto que chamou o método.
this->nomeO atributo nome desse objeto. É o que nome sozinho já quer dizer.
*thisO próprio objeto (seguindo o ponteiro com *).
(*this).nomeIgual 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 isso nome(nome) funciona e é muito comum.

Três costumes: qual usar

CostumeExemploPró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 nomevoid mudarPreco(int novo) { preco = novo; }Sem ambiguidade, sem this. Às vezes é difícil achar um bom nome alternativo.
Atributo com marcaint preco_; … preco_ = preco;Usado na aula public e private. Nunca há conflito, e o getter pode se chamar preco(). É o mais seguro.
Peça ajuda ao g++: a opção -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).

Regra: para encadear, o retorno é 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).

O atributo que não muda (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

EscritaO que faz
thisPonteiro para o objeto que chamou o método. Só existe dentro de métodos (não static).
this->precoO atributo preco do objeto, mesmo que um parâmetro tenha o mesmo nome.
*thisO 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.
-WshadowOpçã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:

void mudarEstoque(int estoque) { = estoque; }

2. Devolver o próprio objeto

Complete o fim do método, para permitir encadear chamadas:

Produto& repor(int q) { estoque += q; return }

3. O tipo de retorno

Complete o tipo de retorno para que caneta.vender(1).vender(2) venda as duas vezes na caneta original:

vender(int q) { estoque -= q; return *this; }

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. nome sozinho, num método, quer dizer this->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). Use this->preco = preco;, outro nome no parâmetro ou o sublinhado no atributo.
  • Na lista de inicialização, preco(preco) funciona sem this.
  • Para encadear chamadas, devolva Classe& e return *this;. Sem o &, a corrente trabalha em cópias e as mudanças se perdem.
  • this == &outro diz se dois nomes são o mesmo objeto.
  • Em C++, this->, nunca this.; e não existe this fora de métodos.
  • Próxima aula: marcar quais métodos só leem o objeto, em Métodos const.