Maison  >  Article  >  développement back-end  >  Une brève analyse des références rvalue, de la sémantique de transfert et du transfert parfait en C 11

Une brève analyse des références rvalue, de la sémantique de transfert et du transfert parfait en C 11

高洛峰
高洛峰original
2017-01-23 14:11:241486parcourir

1. Lvalues ​​​​et rvalues :

C Il n'existe pas de définition standard des lvalues ​​​​et des rvalues, mais il existe un dicton largement reconnu : ceux qui ont des adresses, des noms et des valeurs non temporaires sont les valeurs ; celles qui ne peuvent pas prendre d'adresses, n'ont pas de noms et sont temporaires sont des rvalues ​​

On peut voir que les nombres immédiats, les valeurs renvoyées par les fonctions, etc. sont tous des rvalues ​​au lieu d'objets anonymes ( y compris les variables), les valeurs renvoyées par les fonctions Références, objets const, etc. sont toutes des lvalues ​​

Essentiellement compris, la création et la destruction sont contrôlées par le compilateur en coulisses. valide dans cette ligne de code est rvalue (y compris les données immédiates) ); et celles créées par les utilisateurs, dont la durée de vie peut être connue grâce à des règles de portée, sont des lvalues ​​(y compris les références aux variables locales renvoyées par les fonctions et les objets const), par exemple :

int& foo(){int tmp; return tmp;}
 
int fooo(){int tmp; return tmp;}
 
int a=10;
 
const int b;
 
int& temp=foo();//虽然合法,但temp引用了一个已经不存在的对象
 
int tempp=fooo();

Dans le code ci-dessus, a, temp et foo() sont toutes des lvalues ​​non constantes, b est une lvalue constante, fooo() est une valeur r non constante, 10 est une valeur r constante, il y a une chose qui devrait être spéciale Remarque : La référence renvoyée est une valeur l (peut prendre une adresse) !

De manière générale, le compilateur n'autorise pas les modifications des rvalues ​​​​(car la durée de vie de la rvalue n'est pas contrôlée par le programmeur, même si la rvalue est modifiée, elle peut ne pas être utilisable), en particulier pour les construits -in tapez des objets. , mais C autorise l'utilisation d'objets rvalue pour appeler des fonctions membres. Bien que cela soit autorisé, il est préférable de ne pas le faire pour la même raison.

Référence Rvalue :

.

Référence Rvalue La méthode de représentation est

Datatype&& variable

La référence Rvalue est une nouvelle fonctionnalité de C 11, donc la référence de C 98 est une référence lvalue .La référence Rvalue est utilisée Pour se lier à une rvalue, la durée de vie de la rvalue qui aurait été détruite après avoir été liée à la rvalue sera étendue à la durée de vie de la référence rvalue qui lui est liée. remplacez la référence lvalue, mais utilisez pleinement la construction de rvalues ​​​​(notamment les objets temporaires) pour réduire les opérations de construction et de destruction d'objets afin d'améliorer l'efficacité, par exemple, pour les fonctions suivantes :

(Demo是一个类)
Demo foo(){
  Demo tmp;
  return tmp;
}

En partant du principe que le compilateur n'effectue pas d'optimisation RVO (optimisation de la valeur de retour), les opérations suivantes sont effectuées :

Demo x=foo();

sera appelé trois fois Le constructeur (tmp's,

Demo&& x=foo();

Il n'est alors pas nécessaire de construire x. L'objet temporaire qui devait initialement être détruit verra également sa durée de vie prolongée à la même chose que x en raison de la liaison de x (on peut comprendre que x donne cela (un statut juridique d'un objet temporaire : un nom), l'efficacité doit être améliorée (le prix est que tmp doit occuper 4 octets d'espace, mais c'est trivial).

Règles de liaison pour les références rvalue et les références lvalue :

Les références lvalue constantes peuvent être liées à des constantes et à des lvalues ​​non constantes, à des constantes et à des rvalues ​​non constantes. ;

Les références à valeur l non constantes ne peuvent être liées qu'à des valeurs l non constantes ;

Les références à valeur r non constantes ne peuvent être liées qu'à des valeurs r non constantes (vs2013 peut également être lié aux rvalues ​​constantes);

Les références rvalue constantes ne peuvent être liées qu'à des constantes et des rvalue non consts (la référence rvalue non const n'existe que pour l'exhaustivité sémantique, la référence lvalue constante peut réaliser son rôle

Bien qu'il ressorte des règles de liaison que la référence lvalue constante peut également être liée à une rvalue, mais évidemment la valeur de la rvalue ne peut pas être modifiée. Une référence rvalue peut être utilisée pour obtenir une sémantique de transfert car une référence rvalue. modifie généralement la rvalue liée, la rvalue liée ne peut pas être const .

Remarque : les références rvalue sont des lvalues !

3. Déplacer la sémantique :

L'un des objectifs de rvalue. Les références introduites visent à implémenter la sémantique de déplacement. La sémantique de transfert peut transférer la propriété des ressources (tas, objets système, etc.) d'un objet (généralement un objet temporaire anonyme) à un autre objet, réduisant ainsi les opérations de construction et de destruction d'objets et améliorant le programme. l'efficacité (c'est dans l'exemple 2 qui a été expliqué). La sémantique de transfert et la sémantique de copie sont relatives. À partir de la sémantique de transfert, nous pouvons voir qu'en fait, la sémantique de transfert n'est pas un concept nouveau. bibliothèque de C 98/03, comme l'élision de constructeur de copie dans certains contextes, la copie de pointeurs intelligents (auto_ptr "copy"), l'épissage de liste chaînée (list::splice) et l'échange sur les conteneurs (swap on containers) etc., mais il il n'y a pas de support syntaxique et sémantique unifié

Bien que les fonctions et opérateurs ordinaires puissent également utiliser des références rvalue pour implémenter la sémantique de transfert (comme l'exemple en 2), la sémantique de transfert se fait généralement via des constructeurs de transfert. Elle est implémentée avec l'affectation de transfert opérateur. Le prototype du constructeur de transfert est Classname(Typename&&), et le prototype du constructeur de copie est Classname(const Typename&). Le constructeur de transfert ne sera pas automatiquement généré par le compilateur et doit être défini par vous-même. défini. Le constructeur n'affecte pas la génération par le compilateur d'un constructeur de copie. Si le paramètre passé est une lvalue, le constructeur de copie est appelé

Par exemple :

<.>

    从以上代码可以看出,拷贝构造函数在堆中重新开辟了一个大小为10000的int型数组,然后每个元素分别拷贝,而转移构造函数则是直接接管参数的指针所指向的资源,效率搞下立判!需要注意的是转移构造函数实参必须是右值,一般是临时对象,如函数的返回值等,对于此类临时对象一般在当行代码之后就被销毁,而采用转移构造函数可以延长其生命期,可谓是物尽其用,同时有避免了重新开辟数组.对于上述代码中的转移构造函数,有必要详细分析一下:

Demo(Demo&& lre):arr(lre.arr),size(lre.size)({lre.arr=NULL;}

   

lre是一个右值引用,通过它间接访问实参(临时对象)的资源来完成资源转移,lre绑定的对象(必须)是右值,但lre本身是左值;

因为lre是函数的局部对象,”lre.arr=NULL"必不可少,否则函数结尾调用析构函数销毁lre时仍然会将资源释放,转移的资源还是被系统收回.

4. move()函数

    3中的例子并非万能,Demo(Demo&& lre)的实参必须是右值,有时候一个左值即将到达生存期,但是仍然想要使用转移语义接管它的资源,这时就需要move函数.

    std::move函数定义在标准库fd21907a0e13328ffda092e1790a5d69中,它的作用是将左值强行转化为右值使用,从实现上讲,std:move等同于static_cast5217b58c2bd313af52a58e21ba9922eb(lvalue) ,由此看出,被转化的左值本身的生存期和左值属性并没有被改变,这类似于const_cast函数.因此被move的实参应该是即将到达生存期的左值,否则的话可能起到反面效果.

5. 完美转发(perfect forwarding)

    完美转发指的是将一组实参"完美"地传递给形参,完美指的是参数的const属性与左右值属性不变,例如在进行函数包装的时候,func函数存在下列重载:

void func(const int);
void func(int);
void func(int&&);

   

如果要将它们包装到一个函数cover内,以实现:

void cover(typename para){
  func(para);
}

   

使得针对不同实参能在cover内调用相应类型的函数,似乎只能通过对cover进行函数重载,这使代码变得冗繁,另一种方法就是使用函数模板,但在C++ 11之前,实现该功能的函数模板只能采用值传递,如下:

template<typename T>
void cover(T para){
  ...
  func(para);
  ...
}

   

但如果传递的是一个相当大的对象,又会造成效率问题,要通过引用传递实现形参与实参的完美匹配(包裹const属性与左右值属性的完美匹配),就要使用C++ 11 新引入的引用折叠规则:

函数形参       T的类型         推导后的函数形参

T&               A&                A&
T&               A&&              A&
T&&             A&                A&
T&&             A&&              A&&

 因此,对于前例的函数包装要求,采用以下模板就可以解决:

template<typename T>
void cover(T&& para){
  ...
  func(static_cast<T &&>(para));
  ...
}

如果传入的是左值引用,转发函数将被实例化为:

void func(T& && para){
 
  func(static_cast<T& &&>(para));
 
}

应用引用折叠,就为:

void func(T& para){
 
  func(static_cast<T&>(para));
 
}

如果传入的是右值引用,转发函数将被实例化为:

void func(T&& &¶){
 
   func(static_cast<T&& &&>(para));
}

应用引用折叠,就是:

void func(T&& para){
 
  func(static_cast<T&&>(para));
 
}

对于以上的static_cast5217b58c2bd313af52a58e21ba9922eb ,实际上只在para被推导为右值引用的时候才发挥作用,由于para是左值(右值引用是左值),因此需要将它转为右值后再传入func内,C++ 11在03ebe0a327eab382116018c4d3aba59a定义了一个std::forward8742468051c85b06f0a0af9e3e506b5c函数来实现以上行为,

所以最终版本

template<typename T>
 
void cover(T&& para){
 
  func(forward(forward<T>(para)));
 
}

std::forward的实现与static_cast5217b58c2bd313af52a58e21ba9922eb(para)稍有不同

std::forward函数的用法为forward8742468051c85b06f0a0af9e3e506b5c(para) , 若T为左值引用,para将被转换为T类型的左值,否则para将被转换为T类型右值

总结

以上就是关于C++11中右值引用、转移语义和完美转发的全部内容,这篇文章介绍的很详细,希望对大家的学习工作能有所帮助。

更多浅析C++11中的右值引用、转移语义和完美转发相关文章请关注PHP中文网!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn