Heim >Backend-Entwicklung >C#.Net-Tutorial >Eine kurze Analyse von R-Wert-Referenzen, Übertragungssemantik und perfekter Weiterleitung in C++11

Eine kurze Analyse von R-Wert-Referenzen, Übertragungssemantik und perfekter Weiterleitung in C++11

高洛峰
高洛峰Original
2017-01-23 14:11:241494Durchsuche

1. L-Werte und R-Werte:

Es gibt keine Standarddefinition für L-Werte und R-Werte in C++, aber es gibt ein weithin anerkanntes Sprichwort: diejenigen, die Adressen, Namen usw. annehmen können Nicht-temporäre Werte sind L-Werte; diejenigen, die keine Adressen annehmen können, keine Namen haben und temporär sind, sind R-Werte. Es ist ersichtlich, dass unmittelbare Zahlen, von Funktionen zurückgegebene Werte usw. alle R-Werte sind. Anstelle anonymer Objekte (einschließlich Variablen) sind die von Funktionen zurückgegebenen Werte, Referenzobjekte usw.

Im Wesentlichen werden Erstellung und Zerstörung vom Compiler hinter den Kulissen gesteuert Stellen Sie nur sicher, dass das, was in dieser Codezeile gültig ist, ein R-Wert ist (einschließlich unmittelbarer Daten). Die von Benutzern erstellten Werte, deren Lebensdauer durch Scoping-Regeln ermittelt werden kann, sind L-Werte (einschließlich Verweise auf lokale Variablen, die von Funktionen und Konstanten zurückgegeben werden Objekte), zum Beispiel:

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();

Im obigen Code sind a, temp und foo() alle nicht konstante L-Werte, b ist eine Konstante lvalue, fooo() ist ein nicht konstanter rvalue, 10 ist ein konstanter rvalue, es gibt eine Sache, die besonders sein sollte. Hinweis: Die zurückgegebene Referenz ist ein lvalue (kann eine Adresse annehmen)!

Im Allgemeinen lässt der Compiler keine Änderungen an R-Werten zu (da die Lebensdauer des R-Werts nicht vom Programmierer kontrolliert wird, selbst wenn der R-Wert geändert wird, kann er möglicherweise nicht verwendet werden), insbesondere für erstellte -in-Typ-Objekte, aber C++ erlaubt die Verwendung von R-Wert-Objekten zum Aufrufen von Mitgliedsfunktionen, es ist jedoch aus demselben Grund besser, dies nicht zu tun.

2. R-Wert-Referenz:

R-Wert-Referenz Die Darstellungsmethode ist

Datatype&& variable

R-Wert-Referenz ist eine neue Funktion von C++ 11, daher ist die Referenz in C++ 98 eine L-Wert-Referenz . R-Wert-Referenz wird zum Binden an einen R-Wert verwendet. Die Lebensdauer des R-Werts, der nach der Bindung an den R-Wert zerstört worden wäre, wird auf die Lebensdauer der daran gebundenen R-Wert-Referenz verlängert Ersetzen Sie den l-Wert. Referenz, nutzen Sie jedoch die Konstruktion von r-Werten (insbesondere temporären Objekten) vollständig aus, um die Objektkonstruktions- und -zerstörungsvorgänge zu reduzieren und die Effizienz zu verbessern, beispielsweise für die folgenden Funktionen:

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

Unter der Voraussetzung, dass der Compiler keine RVO-Optimierung (Rückgabewertoptimierung) durchführt, werden die folgenden Vorgänge ausgeführt:

Demo x=foo();

wird dreimal aufgerufen. Der Konstruktor (tmps, xs, temporäre Objekte) ruft auch den Destruktor dreimal auf, wenn das Objekt zerstört wird:

Dann Es besteht keine Notwendigkeit, x zu konstruieren. Die Lebensdauer des temporären Objekts, das ursprünglich zerstört werden sollte, wird aufgrund der Bindung von temporäres Objekt: ein Name), die Effizienz muss verbessert werden (der Preis ist, dass tmp 4 Bytes Platz belegen muss, aber das ist trivial)
Demo&& x=foo();

Bindungsregeln für R-Wert-Referenzen und L-Wert-Referenzen:

Konstante L-Wert-Referenzen können an Konstanten und nicht-konstante L-Werte, Konstanten und nicht-konstante L-Werte gebunden werden;

Nicht konstante L-Wert-Referenzen können nur an nicht konstante L-Werte gebunden werden;

Nicht konstante R-Wert-Referenzen können nur an nicht konstante R-Werte gebunden werden (vs2013 kann auch an konstante R-Werte gebunden werden);

Konstante R-Wert-Referenzen können nur an Konstanten und nicht konstante R-Werte gebunden werden ( Nicht-konstante R-Wert-Referenzen existieren nur zur Vollständigkeit der Semantik, konstante L-Wert-Referenzen können ihre Rolle erfüllen.

Obwohl aus den Bindungsregeln ersichtlich ist, dass konstante L-Wert-Referenzen auch an einen R-Wert gebunden sein können, aber offensichtlich kann der Wert des R-Werts nicht geändert werden, um eine Übertragungssemantik zu erreichen. Da eine R-Wert-Referenz normalerweise den gebundenen R-Wert ändert, kann der gebundene R-Wert nicht konstant sein.

Hinweis: R-Wert-Referenzen sind L-Werte!

3. Move-Semantik:

Einer der Zwecke der Einführung von R-Wert-Referenzen ist die Implementierung der Move-Semantik. Transfer-Semantik kann den Besitz von Ressourcen (Heap, Systemobjekte usw.) übertragen .) von einem Objekt (normalerweise ein anonymes temporäres Objekt) auf ein anderes Objekt, wodurch Objektkonstruktions- und -zerstörungsvorgänge reduziert und die Programmeffizienz verbessert werden (dies wurde im Beispiel von 2 erläutert). Aus der Übertragungssemantik geht hervor, dass die Übertragungssemantik tatsächlich kein neues Konzept ist. Sie wurde tatsächlich in die Sprache und Bibliothek von C++98/03 übernommen, z. B. in einigen Kontexten als Kopierkonstruktor-Elision (auto_ptr „copy“), Linked-List-Splice (list::splice) und Container-Ersetzung (Swap on Containers) usw., es gibt jedoch keine einheitliche Syntax- und Semantikunterstützung

Obwohl dies auch für gewöhnliche Funktionen und Operatoren möglich ist Verwenden Sie R-Wert-Referenzen, um die Übertragungssemantik zu implementieren (wie im Beispiel in 2). Die Übertragungssemantik wird normalerweise durch den Konstruktor und den Übertragungszuweisungsoperator implementiert. Der Prototyp des Übertragungskonstruktors ist Klassenname (Typname &&) und der Prototyp des Der Kopierkonstruktor ist Classname(const Typename&). Der Transferkonstruktor wird vom Compiler nicht automatisch generiert und muss von Ihnen selbst definiert werden , wird der Kopierkonstruktor aufgerufen.

Zum Beispiel:

    从以上代码可以看出,拷贝构造函数在堆中重新开辟了一个大小为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中文网!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn