Tout d'abord, les «qualificatifs de référence pour * ceci» ne sont qu'une «déclaration marketing». Le type de *this
ne change jamais, voir le bas de cet article. Il est cependant beaucoup plus facile de le comprendre avec cette formulation.
Ensuite, le code suivant choisit la fonction à appeler en fonction du qualificatif ref du "paramètre d'objet implicite" de la fonction † :
// 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
}
Production:
$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object
Le tout est fait pour vous permettre de profiter du fait que l'objet auquel la fonction est appelée est une rvalue (temporaire sans nom, par exemple). Prenez le code suivant comme exemple supplémentaire:
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);
}
};
Cela peut être un peu artificiel, mais vous devriez avoir l'idée.
Notez que vous pouvez combiner les qualificatifs cv ( const
et volatile
) et les qualificatifs ref ( &
et &&
).
Remarque: De nombreuses citations standard et une explication de la résolution de surcharge après ici!
† Pour comprendre comment cela fonctionne et pourquoi la réponse de @Nicol Bolas est au moins partiellement fausse, nous devons creuser un peu la norme C ++ (la partie expliquant pourquoi la réponse de @ Nicol est fausse se trouve en bas, si vous êtes seulement intéressé par cela).
La fonction qui sera appelée est déterminée par un processus appelé résolution de surcharge . Ce processus est assez compliqué, donc nous ne toucherons que la partie qui est importante pour nous.
Tout d'abord, il est important de voir comment fonctionne la résolution de surcharge pour les fonctions membres:
§13.3.1 [over.match.funcs]
p2 L'ensemble des fonctions candidates peut contenir des fonctions membres et non membres à résoudre par rapport à la même liste d'arguments. Pour que les listes d'arguments et de paramètres soient comparables dans cet ensemble hétérogène, une fonction membre est considérée comme ayant un paramètre supplémentaire, appelé paramètre d'objet implicite, qui représente l'objet pour lequel la fonction membre a été appelée . [...]
p3 De même, le cas échéant, le contexte peut construire une liste d'arguments qui contient un argument d'objet implicite pour désigner l'objet à opérer.
Pourquoi devons-nous même comparer les fonctions membres et non membres? Surcharge de l'opérateur, c'est pourquoi. Considère ceci:
struct foo{
foo& operator<<(void*); // implementation unimportant
};
foo& operator<<(foo&, char const*); // implementation unimportant
Vous voudriez certainement que ce qui suit appelle la fonction gratuite, n'est-ce pas?
char const* s = "free foo!\n";
foo f;
f << s;
C'est pourquoi les fonctions membres et non membres sont incluses dans le soi-disant jeu de surcharge. Pour rendre la résolution moins compliquée, la partie en gras de la citation standard existe. De plus, c'est le bit important pour nous (même clause):
p4 Pour les fonctions membres non statiques, le type du paramètre d'objet implicite est
où X
est la classe dont la fonction est membre et cv est la qualification cv sur la déclaration de fonction membre. [...]
p5 Pendant la résolution de surcharge, [...] le paramètre d'objet implicite [...] conserve son identité puisque les conversions sur l'argument correspondant doivent respecter ces règles supplémentaires:
aucun objet temporaire ne peut être introduit pour contenir l'argument du paramètre d'objet implicite; et
aucune conversion définie par l'utilisateur ne peut être appliquée pour obtenir une correspondance de type avec elle
[...]
(Le dernier bit signifie simplement que vous ne pouvez pas tricher avec une résolution de surcharge basée sur des conversions implicites de l'objet auquel une fonction membre (ou opérateur) est appelée.)
Prenons le premier exemple en haut de cet article. Après la transformation susmentionnée, l'ensemble de surcharge ressemble à ceci:
void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'
Ensuite, la liste d'arguments, contenant un argument d'objet implicite , est comparée à la liste de paramètres de chaque fonction contenue dans l'ensemble de surcharge. Dans notre cas, la liste d'arguments contiendra uniquement cet argument d'objet. Voyons à quoi cela ressemble:
// 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
Si, après que toutes les surcharges de l'ensemble ont été testées, il n'en reste qu'une, la résolution de surcharge a réussi et la fonction liée à cette surcharge transformée est appelée. Il en va de même pour le deuxième appel à «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
A noter toutefois que, si nous ne fourni aucune ref-qualification (et en tant que telle ne surcharge la fonction), qui f1
serait correspondre à une rvalue (encore §13.3.1
):
p5 [...] Pour les fonctions membres non statiques déclarées sans qualificatif ref , une règle supplémentaire s'applique:
- même si le paramètre d'objet implicite n'est pas
const
qualifié, une valeur r peut être liée au paramètre tant que, à tous autres égards, l'argument peut être converti en type de paramètre d'objet implicite.
struct test{
void f() { std::cout << "lvalue or rvalue object\n"; }
};
int main(){
test t;
t.f(); // OK
test().f(); // OK too
}
Maintenant, pourquoi la réponse de @ Nicol est au moins en partie fausse. Il dit:
Notez que cette déclaration change le type de *this
.
C'est faux, *this
c'est toujours une valeur l:
§5.3.1 [expr.unary.op] p1
L' *
opérateur unaire effectue une indirection : l'expression à laquelle il est appliqué doit être un pointeur vers un type d'objet, ou un pointeur vers un type de fonction et le résultat est une valeur l se référant à l'objet ou à la fonction vers laquelle l'expression pointe.
§9.3.2 [class.this] p1
Dans le corps d'une fonction membre non statique (9.3), le mot this
- clé est une expression de valeur dont la valeur est l'adresse de l'objet pour lequel la fonction est appelée. Le type de this
dans une fonction membre d'une classe X
est X*
. [...]
MyType(int a, double b) &&
:?Il existe un cas d'utilisation supplémentaire pour le formulaire lvalue ref-qualifier. C ++ 98 possède un langage qui permet
const
d'appeler des fonctions non membres pour des instances de classe qui sont des valeurs r. Cela conduit à toutes sortes de bizarreries qui vont à l'encontre du concept même d'évaluation et s'écartent du fonctionnement des types intégrés:Les qualificateurs de référence Lvalue résolvent ces problèmes:
Maintenant, les opérateurs fonctionnent comme ceux des types intégrés, acceptant uniquement les valeurs l.
la source
Disons que vous avez deux fonctions sur une classe, toutes deux portant le même nom et la même signature. Mais l'un d'eux est déclaré
const
:Si une instance de classe ne l'est pas
const
, la résolution de surcharge sélectionnera préférentiellement la version non const. Si l'instance estconst
, l'utilisateur ne peut appeler que laconst
version. Et lethis
pointeur est unconst
pointeur, donc l'instance ne peut pas être modifiée.La "référence de valeur r pour cela" vous permet d'ajouter une autre alternative:
Cela vous permet d'avoir une fonction qui ne peut être appelée que si l'utilisateur l'appelle via une valeur r appropriée. Donc, si c'est dans le type
Object
:De cette façon, vous pouvez spécialiser le comportement en fonction de l'accès à l'objet via une valeur r ou non.
Notez que vous n'êtes pas autorisé à surcharger entre les versions de référence de valeur r et les versions non de référence. Autrement dit, si vous avez un nom de fonction membre, toutes ses versions utilisent les qualificatifs l / r-value sur
this
, ou aucune ne le fait. Vous ne pouvez pas faire ça:Vous devez faire ceci:
Notez que cette déclaration change le type de
*this
. Cela signifie que les&&
versions accèdent à tous les membres en tant que références de valeur r. Il devient donc possible de se déplacer facilement de l'intérieur de l'objet. L'exemple donné dans la première version de la proposition est (remarque: ce qui suit peut ne pas être correct avec la version finale de C ++ 11; il provient directement de la "valeur r de cette proposition"):la source
std::move
la deuxième version, non? Aussi, pourquoi la référence rvalue renvoie-t-elle?*this
, cependant je peux comprendre d'où vient la confusion. Cela est dû au fait que le qualificatif ref modifie le type de paramètre de fonction implicite (ou "caché") auquel "cet objet" (guillemets mis ici exprès!) Est lié pendant la résolution de surcharge et l'appel de fonction. Donc, pas de changement*this
puisque c'est corrigé comme l'explique Xeo. Au lieu de cela, changez le paramètre "hiddden" pour le rendre lvalue- ou rvalue-reference, tout comme leconst
qualificatif de fonction le faitconst
etc.