Primeiro, "ref-qualifiers para * this" é apenas uma "declaração de marketing". O tipo de *this
nunca muda, veja a parte inferior desta postagem. É muito mais fácil entendê-lo com este texto.
Em seguida, o código a seguir escolhe a função a ser chamada com base no qualificador ref do "parâmetro de objeto implícito" da função † :
// t.cpp
#include <iostream>
struct test{
void f() &{ std::cout << "lvalue object\n"; }
void f() &&{ std::cout << "rvalue object\n"; }
};
int main(){
test t;
t.f(); // lvalue
test().f(); // rvalue
}
Resultado:
$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object
Tudo é feito para permitir que você aproveite o fato de quando o objeto em que a função é chamada é um rvalue (temporário sem nome, por exemplo). Tome o seguinte código como um exemplo adicional:
struct test2{
std::unique_ptr<int[]> heavy_resource;
test2()
: heavy_resource(new int[500]) {}
operator std::unique_ptr<int[]>() const&{
// lvalue object, deep copy
std::unique_ptr<int[]> p(new int[500]);
for(int i=0; i < 500; ++i)
p[i] = heavy_resource[i];
return p;
}
operator std::unique_ptr<int[]>() &&{
// rvalue object
// we are garbage anyways, just move resource
return std::move(heavy_resource);
}
};
Isso pode ser um pouco artificial, mas você deve entender.
Observe que você pode combinar os qualificadores cv ( const
e volatile
) e ref-qualificadores ( &
e &&
).
Nota: Muitas citações padrão e explicação de resolução de sobrecarga depois daqui!
† Para entender como isso funciona e por que a resposta de @Nicol Bolas está pelo menos parcialmente errada, precisamos cavar um pouco o padrão C ++ (a parte que explica por que a resposta de @ Nicol está errada está na parte inferior, se você estiver interessado apenas nisso).
Qual função será chamada é determinada por um processo chamado resolução de sobrecarga . Esse processo é bastante complicado, portanto, tocaremos apenas o que é importante para nós.
Primeiro, é importante ver como a resolução de sobrecarga para funções-membro funciona:
§13.3.1 [over.match.funcs]
p2 O conjunto de funções candidatas pode conter funções membro e não membro a serem resolvidas na mesma lista de argumentos. Para que as listas de argumentos e parâmetros sejam comparáveis nesse conjunto heterogêneo, uma função membro é considerada como tendo um parâmetro extra, chamado parâmetro implícito do objeto, que representa o objeto para o qual a função membro foi chamada . [...]
p3 Da mesma forma, quando apropriado, o contexto pode construir uma lista de argumentos que contém um argumento de objeto implícito para indicar o objeto a ser operado.
Por que precisamos comparar funções de membros e de não membros? Sobrecarga de operador, é por isso. Considere isto:
struct foo{
foo& operator<<(void*); // implementation unimportant
};
foo& operator<<(foo&, char const*); // implementation unimportant
Você certamente gostaria que o seguinte chamasse a função livre, não é?
char const* s = "free foo!\n";
foo f;
f << s;
É por isso que as funções membro e não membro são incluídas no chamado conjunto de sobrecarga. Para tornar a resolução menos complicada, existe a parte em negrito da cotação padrão. Além disso, esta é a parte importante para nós (mesma cláusula):
p4 Para funções-membro não estáticas, o tipo do parâmetro implícito do objeto é
onde X
é a classe da qual a função é membro e cv é a qualificação cv na declaração da função de membro. [...]
p5 Durante a resolução de sobrecarga, [...] o parâmetro implícito do objeto [...] mantém sua identidade, pois as conversões no argumento correspondente devem obedecer a estas regras adicionais:
nenhum objeto temporário pode ser introduzido para conter o argumento do parâmetro implícito do objeto; e
nenhuma conversão definida pelo usuário pode ser aplicada para obter uma correspondência de tipo com ela
[...]
(O último bit significa apenas que você não pode enganar a resolução de sobrecarga com base nas conversões implícitas do objeto em que uma função membro (ou operador) é chamada.)
Vamos dar o primeiro exemplo na parte superior deste post. Após a transformação mencionada acima, o conjunto de sobrecargas se parece com isso:
void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'
Em seguida, a lista de argumentos, contendo um argumento de objeto implícito , é comparada com a lista de parâmetros de todas as funções contidas no conjunto de sobrecargas. No nosso caso, a lista de argumentos conterá apenas esse argumento do objeto. Vamos ver como isso se parece:
// first call to 'f' in 'main'
test t;
f1(t); // 't' (lvalue) can match 'test&' (lvalue reference)
// kept in overload-set
f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference)
// taken out of overload-set
Se todas as sobrecargas no conjunto forem testadas, apenas uma permanecerá, a resolução da sobrecarga foi bem-sucedida e a função vinculada a essa sobrecarga transformada será chamada. O mesmo vale para a segunda chamada para 'f':
// second call to 'f' in 'main'
f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference)
// taken out of overload-set
f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference)
// kept in overload-set
Nota, contudo, que, se não tivéssemos fornecido qualquer ref-qualificação (e, como tal, não sobrecarregado da função), que f1
iria coincidir com um rvalue (ainda §13.3.1
):
p5 [...] Para funções membro não estáticas declaradas sem um ref-qualifier , uma regra adicional se aplica:
- mesmo que o parâmetro implícito do objeto não seja
const
qualificado, um rvalue pode ser vinculado ao parâmetro desde que em todos os outros aspectos o argumento possa ser convertido no tipo do parâmetro implícito do objeto.
struct test{
void f() { std::cout << "lvalue or rvalue object\n"; }
};
int main(){
test t;
t.f(); // OK
test().f(); // OK too
}
Agora, sobre por que a resposta de @ Nicol está pelo menos parcialmente errada. Ele diz:
Observe que esta declaração altera o tipo de *this
.
Isso está errado, *this
é sempre um lvalue:
§5.3.1 [expr.unary.op] p1
O *
operador unário executa indireção : a expressão à qual é aplicada deve ser um ponteiro para um tipo de objeto ou um ponteiro para um tipo de função e o resultado é um valor l que se refere ao objeto ou função para a qual a expressão aponta.
§9.3.2 [class.this] p1
No corpo de uma função de membro não estática (9.3), a palavra this
- chave é uma expressão de prvalor cujo valor é o endereço do objeto para o qual a função é chamada. O tipo de this
em uma função de membro de uma classe X
é X*
. [...]
MyType(int a, double b) &&
:?Há um caso de uso adicional para o formulário lvalue ref-qualifier. O C ++ 98 possui uma linguagem que permite que
const
funções que não são membros sejam chamadas para instâncias de classe que são rvalues. Isso leva a todos os tipos de estranheza que são contrários ao próprio conceito de valorização e se desviam de como os tipos internos funcionam:Os qualificadores ref do Lvalue resolvem estes problemas:
Agora, os operadores trabalham como os tipos internos, aceitando apenas lvalues.
fonte
Digamos que você tenha duas funções em uma classe, ambas com o mesmo nome e assinatura. Mas um deles é declarado
const
:Se uma instância de classe não for
const
, a resolução de sobrecarga selecionará preferencialmente a versão não const. Se a instância forconst
, o usuário poderá chamar apenas aconst
versão. E othis
ponteiro é umconst
ponteiro, portanto, a instância não pode ser alterada.O que a "referência de valor r para isso` faz é permitir que você adicione outra alternativa:
Isso permite que você tenha uma função que só pode ser chamada se o usuário chamar através de um valor-r adequado. Portanto, se este for do tipo
Object
:Dessa forma, você pode especializar o comportamento com base no fato de o objeto estar sendo acessado via valor-r ou não.
Observe que você não pode sobrecarregar entre as versões de referência com valor r e as versões sem referência. Ou seja, se você tiver um nome de função de membro, todas as suas versões usam os qualificadores de valor l / r
this
ou nenhum deles. Você não pode fazer isso:Você deve fazer isso:
Observe que esta declaração altera o tipo de
*this
. Isso significa que&&
todas as versões acessam membros como referências de valor-r. Portanto, torna-se possível mover-se facilmente de dentro do objeto. O exemplo dado na primeira versão da proposta é (nota: o seguinte pode não estar correto com a versão final do C ++ 11; é direto do "valor r inicial desta" proposta inicial):fonte
std::move
a segunda versão, não? Além disso, por que a referência rvalue retorna?*this
, no entanto, eu posso entender de onde vem a confusão. Isso ocorre porque o ref-qualifier altera o tipo de parâmetro implícito (ou "oculto") para o qual o objeto "this" (aspas colocadas de propósito aqui!) É vinculado durante a resolução de sobrecarga e a chamada de função. Portanto, nenhuma alteração,*this
pois isso é corrigido, como o Xeo explica. Em vez disso mudar de parâmetro "hiddden" para torná-lo lvalue- ou rvalue referência, assim comoconst
qualificador função tornaconst
etc ..