Sobrecarregando operadores de acesso a membros ->,. *

129

Eu entendo mais sobrecarga de operadores, com excepção dos operadores de acesso membro ->, .*, ->*etc.

Em particular, o que é passado para essas funções do operador e o que deve ser retornado?

Como o operador funciona (por exemplo operator->(...)) sabe a que membro está sendo referido? Ele pode saber? Precisa mesmo saber?

Finalmente, existem considerações constantes que precisam ser levadas em consideração? Por exemplo, ao sobrecarregar algo como operator[], geralmente você precisará de uma versão const e não-const. Os operadores de acesso a membros exigem versões const e não-const?

Bingo
fonte
1
Eu acredito que o C ++ acima - O FAQ toca em todos os Qs solicitados no Q acima.
Alok Save
conste não constversões operator->não são necessárias , mas fornecer ambos pode ser útil.
21812 Fred
1
Veja também: yosefk.com/c++fqa/operator.html
György Andrasek
9
@ Als: O FAQ não explica como sobrecarregar ->*e .*. Na verdade, nem sequer os menciona! Eu sinto que eles são raros em estar em uma FAQ, mas gostaria de vincular esta pergunta a partir da FAQ. Por favor, não feche isso como uma fraude do FAQ!
Sbi 19/01
@sbi, eu simplesmente não consegui encontrar um link para esta pergunta no seu (incrível) FAQ, e acabei fazendo uma pergunta duplicada. Você poderia torná-lo mais óbvio? (desculpas se já é óbvio).
P i

Respostas:

144

->

Este é o único realmente complicado. Deve ser uma função de membro não estática e não requer argumentos. O valor de retorno é usado para executar a consulta do membro.

Se o valor de retorno for outro objeto do tipo de classe, não um ponteiro, a pesquisa subsequente do membro também será tratada por uma operator->função. Isso é chamado de "comportamento de detalhamento". O idioma une as operator->chamadas até a última retornar um ponteiro.

struct client
    { int a; };

struct proxy {
    client *target;
    client *operator->() const
        { return target; }
};

struct proxy2 {
    proxy *target;
    proxy &operator->() const
        { return * target; }
};

void f() {
    client x = { 3 };
    proxy y = { & x };
    proxy2 z = { & y };

    std::cout << x.a << y->a << z->a; // print "333"
}

->*

Este é apenas complicado, pois não há nada de especial nisso. A versão não sobrecarregada requer um objeto de ponteiro para o tipo de classe no lado esquerdo e um objeto de ponteiro para o tipo de membro à direita. Mas quando você o sobrecarrega, pode pegar os argumentos que quiser e retornar o que quiser. Nem precisa ser um membro não estático.

Em outras palavras, este é apenas um operador binário normal como +, -, e /. Veja também: O operador livre -> * sobrecarrega o mal?

.* e .

Estes não podem ser sobrecarregados. Já existe um significado interno quando o lado esquerdo é do tipo de classe. Talvez faça algum sentido poder defini-los para um ponteiro no lado esquerdo, mas o comitê de design de idiomas decidiu que seria mais confuso do que útil.

Sobrecarga ->, ->*, ., e .*só pode preencher nos casos em que uma expressão seria indefinida, ele nunca pode mudar o significado de uma expressão que seria válido sem sobrecarga.

Potatoswatter
fonte
2
Sua última afirmação não é completamente verdadeira. Por exemplo, você pode sobrecarregar o newoperador, mesmo que seja válido mesmo quando não estiver sobrecarregado.
Matt
6
O @Matt newestá sempre sobrecarregado ou as regras de sobrecarga não se aplicam a ele (13.5 / 5: as funções de alocação e desalocação, operador novo, operador novo [], operador novo [], operador excluir e operador excluir []) são descritas completamente em 3.7 .4. os atributos e restrições encontradas no resto deste subitem não se aplicam a eles a menos que explicitamente indicado em 3.7.4.) Mas a sobrecarga unário &ou binário &&, ||ou ,, ou a adição de sobrecargas de operator=, ou sobrecarregar qualquer coisa por um sem escopo tipo de enumeração, pode alterar o significado de uma expressão. Esclareceu a declaração, obrigado!
Potatoswatter
41

Operador -> é especial.

"Possui restrições atípicas adicionais: ele deve retornar um objeto (ou referência a um objeto) que também possui um operador de desreferência de ponteiro, ou deve retornar um ponteiro que possa ser usado para selecionar o que a seta do operador de desreferência de ponteiro está apontando. " Bruce Eckel: Pensando em CPP Vol-one: operador->

A funcionalidade extra é fornecida por conveniência, para que você não precise ligar

a->->func();

Você pode simplesmente fazer:

a->func();

Isso torna o operador -> diferente das outras sobrecargas do operador.

Totonga
fonte
3
Esta resposta merece mais crédito, você pode fazer o download do livro de Eckel a partir desse link e as informações estão no capítulo 12 do volume um.
P i
26

Você não pode sobrecarregar o acesso dos membros .(ou seja, a segunda parte do que ->faz). No entanto, pode sobrecarregar o unário dereferencing operador *(ou seja, a primeira parte do que ->faz).

O ->operador C ++ é basicamente a união de duas etapas e isso fica claro se você pensa que x->yé equivalente a (*x).y. O C ++ permite que você personalize o que fazer com a (*x)parte quando xé uma instância da sua classe.

A semântica para ->sobrecarga é um tanto estranha, pois o C ++ permite retornar um ponteiro regular (que será usado para encontrar o objeto apontado) ou retornar uma instância de outra classe se essa classe também fornecer um ->operador. Quando, neste segundo caso, a procura pelo objeto desreferenciado continua a partir desta nova instância.

6502
fonte
2
Ótima explicação! Eu acho que isso significa o mesmo para ->*, pois é equivalente à forma de (*x).*?
Bingo
10

O ->operador não sabe para qual membro está sendo apontado, apenas fornece um objeto para executar o acesso real do membro.

Além disso, não vejo razão para que você não possa fornecer versões const e non-const.

John Chadwick
fonte
7

Quando você sobrecarrega o operador -> () (nenhum argumento é passado aqui), o que o compilador realmente faz é chamar -> recursivamente até retornar um ponteiro real para um tipo. Em seguida, ele usa o membro / método correto.

Isso é útil, por exemplo, para criar uma classe de ponteiro inteligente que encapsule o ponteiro real. O operador sobrecarregado-> é chamado, faz o que faz (por exemplo, travar para segurança da thread), retorna o ponteiro interno e, em seguida, o compilador chama -> para esse ponteiro interno.

Quanto à constância, ela foi respondida nos comentários e em outras respostas (você pode e deve fornecer as duas).

Asaf
fonte