Copier la sémantique et la gestion des ressources en C ++


En C ++, le programmeur doit décider comment les ressources utilisées seront libérées; il n'y a pas d'outils automatiques comme le garbage collector. L'article discute des solutions possibles à ce problème, examine en détail les problèmes potentiels, ainsi qu'un certain nombre de problèmes connexes.




Table des matières




Présentation


La gestion des ressources est quelque chose qu'un programmeur C ++ doit faire tout le temps. Les ressources incluent les blocs de mémoire, les objets du noyau du système d'exploitation, les verrous multithread, les connexions réseau, les connexions de base de données et tout autre objet créé dans la mémoire dynamique. L'accès à la ressource se fait par un descripteur, le type du descripteur est généralement un pointeur ou l'un de ses alias ( HANDLE , etc.), parfois le tout (descripteurs de fichiers UNIX). Après avoir utilisé la ressource, vous devez la libérer, sinon tôt ou tard une application qui ne libère pas de ressources (et éventuellement d'autres applications) manquera de ressources. Ce problème est très aigu, nous pouvons dire que l'une des principales caractéristiques du .NET, de Java et de plusieurs autres plates-formes est un système de gestion des ressources unifié basé sur le garbage collection.


Les fonctionnalités orientées objet de C ++ conduisent naturellement à la solution suivante: la classe qui gère la ressource contient le descripteur de ressource en tant que membre, initialise le descripteur lorsque la ressource est capturée et libère la ressource dans le destructeur. Mais après une certaine réflexion (ou expérience) vient la compréhension que ce n'est pas si simple. Et le problème principal est la sémantique de la copie. Si la classe qui gère la ressource utilise le constructeur de copie généré par le compilateur par défaut, alors après avoir copié l'objet, nous obtiendrons deux copies du handle de la même ressource. Si un objet libère une ressource, le second pourra ensuite essayer d'utiliser ou de libérer la ressource déjà libérée, ce qui est en tout cas incorrect et peut conduire au comportement dit non défini, c'est-à-dire que tout peut arriver, par exemple, une interruption anormale du programme.


Heureusement, en C ++, un programmeur peut contrôler entièrement le processus de copie en définissant lui-même un constructeur de copie et un opérateur d'affectation de copie, ce qui nous permet de résoudre le problème ci-dessus, et généralement de plusieurs manières. La mise en œuvre de la copie doit être étroitement liée au mécanisme de libération de la ressource, et nous l'appellerons collectivement la stratégie de propriété de la copie. La soi-disant «règle des trois grands» est bien connue, qui stipule que si un programmeur définit au moins une des trois opérations - constructeur de copie, opérateur d'affectation de copie ou destructeur - alors il doit définir les trois opérations. Les stratégies de propriété de copie spécifient simplement comment procéder. Il existe quatre stratégies de base de propriété de la copie.



1. Stratégies de base de propriété de copie


Avant la capture de la ressource ou après sa libération, le descripteur doit prendre une valeur spéciale indiquant qu'il n'est pas associé à la ressource. Il s'agit généralement de zéro, parfois -1, converti en un type de descripteur. Dans tous les cas, un tel descripteur sera appelé zéro. La classe qui gère la ressource doit reconnaître le descripteur nul et ne pas essayer d'utiliser ou de libérer la ressource dans ce cas.



1.1. Stratégie d'interdiction de copie


C'est la stratégie la plus simple. Dans ce cas, il est simplement interdit de copier et d'assigner des instances de classe. Le destructeur libère la ressource capturée. En C ++, pour interdire la copie n'est pas difficile, la classe doit déclarer, mais pas définir, le constructeur de copie fermée et l'opérateur d'affectation de copie.


 class X { private:    X(const X&);    X& operator=(const X&); // ... }; 

Les tentatives de copie sont contrecarrées par le compilateur et l'éditeur de liens.


Le standard C ++ 11 propose une syntaxe spéciale pour ce cas:


 class X { public:    X(const X&) = delete;    X& operator=(const X&) = delete; // ... }; 

Cette syntaxe est plus visuelle et donne des messages plus compréhensibles au compilateur lors de la tentative de copie.


Dans la version précédente de la bibliothèque standard (C ++ 98), les classes de flux d'entrée / sortie ( std::fstream , etc.) utilisaient la stratégie d'interdiction de copie, et sous Windows, de nombreuses classes de MFC ( CFile , CEvent , CMutex , etc.). Dans la bibliothèque standard C ++ 11, certaines classes utilisent cette stratégie pour prendre en charge la synchronisation multi-thread.



1.2. Stratégie de propriété exclusive


Dans ce cas, lors de l'implémentation de la copie et de l'affectation, le descripteur de ressource se déplace de l'objet source vers l'objet cible, c'est-à-dire qu'il reste dans une seule copie. Après la copie ou l'affectation, l'objet source a un descripteur nul et ne peut pas utiliser la ressource. Le destructeur libère la ressource capturée. Les termes propriété exclusive ou stricte [Josuttis] sont également utilisés pour cette stratégie; Andrei Alexandrescu utilise le terme copie destructrice. En C ++ 11, cela se fait comme suit: la copie régulière et l'affectation de copie sont interdites de la manière décrite ci-dessus, et la sémantique des mouvements est implémentée, c'est-à-dire que le constructeur de déplacement et l'opérateur d'affectation de déplacement sont définis. (Plus d'informations sur la sémantique du mouvement plus tard.)


 class X { public:    X(const X&) = delete;    X& operator=(const X&) = delete;    X(X&& src) noexcept;    X& operator=(X&& src) noexcept; // ... }; 

Ainsi, la stratégie de propriété exclusive peut être considérée comme une extension de la stratégie d'interdiction de copie.


Dans la bibliothèque standard C ++ 11, cette stratégie utilise le pointeur intelligent std::unique_ptr<> et certaines autres classes, par exemple: std::thread , std::unique_lock<> , ainsi que les classes qui utilisaient auparavant la stratégie d'interdiction de copie ( std::fstream , etc.). Sous Windows, les classes MFC qui utilisaient auparavant la stratégie d'interdiction de copie ont également commencé à utiliser la stratégie de propriété exclusive ( CFile , CEvent , CMutex , etc.).



1.3. Stratégie de copie approfondie


Dans ce cas, vous pouvez copier et affecter des instances de classe. Il est nécessaire de définir le constructeur de copie et l'opérateur d'affectation de copie, de sorte que l'objet cible copie la ressource sur lui-même à partir de l'objet source. Après cela, chaque objet possède sa copie de la ressource, peut indépendamment utiliser, modifier et libérer la ressource. Le destructeur libère la ressource capturée. Parfois, pour les objets qui utilisent la stratégie de copie approfondie, le terme objets de valeur est utilisé.


Cette stratégie ne s'applique pas à toutes les ressources. Il peut être appliqué à des ressources associées à un tampon de mémoire, telles que des chaînes, mais il n'est pas très clair comment l'appliquer à des objets du noyau du système d'exploitation tels que des fichiers, des mutex, etc.


La stratégie de copie profonde est utilisée dans tous les types de chaînes d'objets, std::vector<> et d'autres conteneurs de la bibliothèque standard.



1.4. Stratégie de copropriété


Dans ce cas, vous pouvez copier et affecter des instances de classe. Vous devez définir le constructeur de copie et l'opérateur d'affectation de copie dans lesquels le descripteur de ressource (ainsi que d'autres données) est copié, mais pas la ressource elle-même. Après cela, chaque objet a sa propre copie du descripteur, peut utiliser, modifier, mais ne peut pas libérer la ressource, tant qu'il y a au moins un objet supplémentaire qui possède une copie du descripteur. Une ressource est libérée une fois que le dernier objet qui possède une copie du descripteur est hors de portée. Comment cela peut être mis en œuvre est décrit ci-dessous.


Les stratégies de copropriété sont souvent utilisées par les pointeurs intelligents, et il est également naturel de les utiliser pour des ressources immuables. Le pointeur intelligent std::shared_ptr<> implémente cette stratégie dans la bibliothèque standard C ++ 11.



2. Stratégie Deep Copy - Problèmes et solutions


Considérons un modèle pour la fonction d'échange d'états d'objets de type T dans la bibliothèque standard C ++ 98.


 template<typename T> void swap(T& a, T& b) {    T tmp(a);    a = b;    b = tmp; } 

Si le type T possède une ressource et utilise une stratégie de copie approfondie, nous avons trois opérations pour allouer une nouvelle ressource, trois opérations de copie et trois opérations pour libérer des ressources. Alors que dans la plupart des cas, cette opération peut être effectuée sans allouer de nouvelles ressources et sans copier du tout, il suffit que les objets échangent des données internes, y compris un descripteur de ressource. Il existe de nombreux exemples similaires lorsque vous devez créer des copies temporaires d'une ressource et les libérer immédiatement. Une telle mise en œuvre inefficace des opérations quotidiennes a stimulé la recherche de solutions pour leur optimisation. Examinons les principales options.



2.1. Copie sur dossier


La copie sur écriture (COW), également appelée copie différée, peut être considérée comme une tentative de combiner une stratégie de copie approfondie et une stratégie de propriété partagée. Initialement, lors de la copie d'un objet, le descripteur de ressource est copié sans la ressource elle-même, et pour les propriétaires, la ressource devient partagée et en lecture seule, mais dès qu'un propriétaire doit modifier la ressource partagée, la ressource est copiée, puis ce propriétaire travaille avec son une copie. L'implémentation de COW résout le problème de l'échange d'états: il n'y a pas d'allocation supplémentaire de ressources et de copie. L'utilisation de COW est très populaire lors de l'implémentation de chaînes; par exemple, CString (MFC, ATL). Une discussion sur les moyens possibles de mettre en œuvre la GC et les problèmes émergents peut être trouvée dans [Meyers1], [Sutter]. [Guntheroth] a proposé une implémentation COW en utilisant std::shared_ptr<> . Il y a des problèmes lors de l'implémentation de COW dans un environnement multi-thread, c'est pourquoi il est interdit d'utiliser COW pour les chaînes dans la bibliothèque C ++ 11 standard, voir [Josuttis], [Guntheroth].


Le développement de l'idée COW conduit au schéma de gestion des ressources suivant: la ressource est immuable et gérée par des objets en utilisant la stratégie de propriété partagée, si nécessaire, pour changer la ressource, une nouvelle ressource modifiée de manière appropriée est créée et un nouvel objet propriétaire est renvoyé. Ce schéma est utilisé pour les chaînes et autres objets immuables sur les plates-formes .NET et Java. En programmation fonctionnelle, il est utilisé pour des structures de données plus complexes.



2.2. Définition d'une fonction d'échange d'état pour une classe


Il a été démontré ci-dessus à quel point la fonction d'échange d'état peut être inefficace, mise en œuvre de manière simple, par copie et affectation. Et il est utilisé assez largement, par exemple, il est utilisé par de nombreux algorithmes de la bibliothèque standard. Pour que les algorithmes n'utilisent pas un autre std::swap() , mais une autre fonction spécifiquement définie pour la classe, deux étapes doivent être effectuées.


1. Définissez dans la classe une fonction membre Swap() (le nom n'est pas important) qui implémente l'échange d'états.


 class X { public:    void Swap(X& other) noexcept; // ... }; 

Vous devez vous assurer que cette fonction ne noexcept pas d'exceptions; en C ++ 11, ces fonctions doivent être déclarées comme noexcept .


2. Dans le même espace de noms que la classe X (généralement dans le même fichier d'en-tête), définissez la fonction swap() libre (non membre) comme suit (le nom et la signature sont fondamentaux):


 inline void swap(X& a, X& b) noexcept { a.Swap(b); } 

Après cela, les algorithmes de la bibliothèque standard l'utiliseront, pas std::swap() . Cela fournit un mécanisme appelé recherche dépendante des arguments (ADL). Pour plus d'informations sur ADL, voir [Dewhurst1].


Dans la bibliothèque standard C ++, tous les conteneurs, pointeurs intelligents et autres classes implémentent la fonction d'échange d'état comme décrit ci-dessus.


La fonction membre Swap() est généralement facilement définie: il est nécessaire d'appliquer séquentiellement une opération d'échange d'état aux bases de données et aux membres, s'ils le prennent en charge, et std::swap() sinon.


La description ci-dessus est quelque peu simplifiée, une description plus détaillée peut être trouvée dans [Meyers2]. Une discussion des questions liées à la fonction d'échange d'État peut également être trouvée dans [Sutter / Alexandrescu].


La fonction d'échange d'état peut être attribuée à l'une des opérations de base de la classe. En l'utilisant, vous pouvez définir gracieusement d'autres opérations. Par exemple, l'opérateur d'affectation de copie est défini via copy et Swap() comme suit:


 X& X::operator=(const X& src) {    X tmp(src);    Swap(tmp);    return *this; } 

Ce modèle est appelé l'idiome de copie et d'échange ou l'idiome Herb Sutter, pour plus de détails, voir [Sutter], [Sutter / Alexandrescu], [Meyers2]. Sa modification peut être appliquée pour implémenter la sémantique du déplacement, voir les sections 2.4, 2.6.1.



2.3. Suppression des copies intermédiaires par le compilateur


Considérez la classe


 class X { public:    X(/*  */); // ... }; 

Et fonction


 X Foo() { // ...    return X(/*  */); } 

Avec une approche simple, le retour de la fonction Foo() est réalisé en copiant l'instance de X Mais les compilateurs sont capables de supprimer l'opération de copie du code, l'objet est créé directement au point d'appel. C'est ce qu'on appelle l'optimisation de la valeur de retour (RVO). RVO est utilisé par les développeurs de compilateurs depuis un certain temps et est actuellement corrigé dans la norme C ++ 11. Bien que la décision sur RVO soit prise par le compilateur, le programmeur peut écrire du code en fonction de son utilisation. Pour ce faire, il est souhaitable que la fonction ait un point de retour et que le type de l'expression retournée corresponde au type de la valeur de retour de la fonction. Dans certains cas, il est conseillé de définir un constructeur fermé spécial appelé «constructeur de calcul», pour plus de détails, voir [Dewhurst2]. Le RVO est également discuté dans [Meyers3] et [Guntheroth].


Les compilateurs peuvent supprimer des copies intermédiaires dans d'autres situations.



2.4. Implémentation de la sémantique du déplacement


L'implémentation de la sémantique de déplacement consiste à définir un constructeur de déplacement qui a un paramètre de type rvalue-référence à la source et un opérateur d'affectation de déplacement avec le même paramètre.


Dans la bibliothèque standard C ++ 11, le modèle de fonction d'échange d'état est défini comme suit:


 template<typename T> void swap(T& a, T& b) {    T tmp(std::move(a));    a = std::move(b);    b = std::move(tmp); } 

Conformément aux règles de résolution des surcharges de fonctions ayant des paramètres de type rvalue-reference (voir Annexe A), dans le cas où le type T a un constructeur mobile et un opérateur d'affectation mobile, ils seront utilisés, et il n'y aura pas d'allocation de ressources temporaires et de copie. Sinon, le constructeur de copie et l'opérateur d'affectation de copie seront utilisés.


L'utilisation de la sémantique de la délocalisation évite de créer des copies temporaires dans un contexte beaucoup plus large que la fonction d'échange d'état décrite ci-dessus. La sémantique de mouvement s'applique à toute valeur rvalue, c'est-à-dire une valeur temporaire sans nom, ainsi qu'à la valeur de retour d'une fonction si elle a été créée localement (y compris une lvalue), et RVO n'a pas été appliqué. Dans tous ces cas, il est garanti que l'objet source ne peut en aucun cas être utilisé après le déplacement. La sémantique de déplacement s'applique également à la valeur lvalue à laquelle la transformation std::move() est appliquée. Mais dans ce cas, le programmeur est responsable de la façon dont les objets source seront utilisés après le déplacement (exemple std::swap() ).


La bibliothèque standard C ++ 11 a été repensée en tenant compte de la sémantique du mouvement. De nombreuses classes ont ajouté un constructeur de déplacement et un opérateur d'affectation de déplacement, ainsi que d'autres fonctions membres, avec des paramètres de type référence rvalue. Par exemple, std::vector<T> a une version surchargée de void push_back(T&& src) . Tout cela permet dans de nombreux cas d'éviter de créer des copies temporaires.


L'implémentation de la sémantique de déplacement n'annule pas les définitions de la fonction d'échange d'état pour une classe. Une fonction d'échange d'état spécialement définie peut être plus efficace que la norme std::swap() . De plus, le constructeur de déplacement et l'opérateur d'affectation de déplacement sont très facilement définis à l'aide de la fonction membre de l'échange d'états comme suit (variation de l'idiome de copie et d'échange):


 class X { public:    X() noexcept {/*    */}    void Swap(X& other) noexcept {/*   */}    X(X&& src) noexcept : X()    {        Swap(src);    }    X& operator=(X&& src) noexcept    {        X tmp(std::move(src)); //         Swap(tmp);        return *this;    } // ... }; 

Le constructeur de déplacement et l'opérateur d'affectation de déplacement sont les fonctions membres pour lesquelles il est hautement souhaitable de s'assurer qu'ils ne noexcept pas d'exceptions et sont par conséquent déclarés comme noexcept . Cela vous permet d'optimiser certaines opérations des conteneurs de bibliothèque standard sans violer la stricte garantie de sécurité des exceptions, voir [Meyers3] et [Guntheroth] pour plus de détails. Le modèle proposé offre une telle garantie, à condition que le constructeur par défaut et la fonction membre de l'échange d'états ne lèvent pas d'exceptions.


La norme C ++ 11 prévoit que le compilateur génère automatiquement un constructeur mobile et un opérateur d'affectation mobile. Pour ce faire, ils doivent être déclarés à l'aide de la construction "=default" .


 class X { public:    X(X&&) = default;    X& operator=(X&&) = default; // ... }; 

Les opérations sont implémentées en appliquant séquentiellement l'opération de déplacement aux bases et aux membres de la classe, s'ils prennent en charge le déplacement, et en copiant les opérations dans le cas contraire. Il est clair que cette option est loin d'être toujours acceptable. Les descripteurs bruts ne se déplacent pas, mais vous ne pouvez généralement pas les copier. Dans certaines conditions, le compilateur peut générer indépendamment un constructeur mobile et un opérateur d'affectation mobile similaires, mais il est préférable de ne pas utiliser cette opportunité, ces conditions sont plutôt déroutantes et peuvent facilement changer lorsque la classe est affinée. Voir [Meyers3] pour plus de détails.


En général, la mise en œuvre et l'utilisation de la sémantique du déplacement est une «chose subtile». Le compilateur peut appliquer la copie là où le programmeur attend un déplacement. Voici quelques règles pour éliminer ou au moins réduire la probabilité d'une telle situation.


  1. Si possible, utilisez l'interdiction de copie.
  2. Déclarez le constructeur de déplacement et l'opérateur d'affectation de déplacement en tant que noexcept .
  3. Implémentez la sémantique des mouvements pour les classes de base et les membres.
  4. Appliquez la transformation std::move() aux paramètres des fonctions de type référence rvalue.

La règle 2 a été discutée ci-dessus. 4 , rvalue- lvalue (. ). .


 class B { // ...    B(B&& src) noexcept; }; class D : public B { // ...    D(D&& src) noexcept; }; D::D(D&& src) noexcept    : B(std::move(src)) //  {/* ... */} 

, . 6.2.1.



2.5. vs.


, RVO (. 2.3), , . ( ), , . , . C++11 - emplace() , emplace_front() , emplace_back() , . , - — (variadic templates), . , C++11 — .


:


  1. , , .
  2. , , .

, .


 std::vector<std::string> vs; vs.push_back(std::string(3, 'X')); //  vs.emplace_back(3, '7');           //  

std::string , . . , , . , [Meyers3].



2.6. Résumé


, , . - . . — : , . , , , . : , , «» .


: , , .NET Java. , Clone() Duplicate() .


- - , :


  1. .
  2. .
  3. - rvalue-.

.NET Java - , , .NET IClonable . , .



3.


, . - , . , . Windows: , HANDLE , COM-. DuplicateHandle() , CloseHandle() . COM- - IUnknown::AddRef() IUnknown::Release() . ATL ComPtr<> , COM- . UNIX, C, _dup() , .


C++11 std::shared_ptr<> . , , , , , . , . std::shared_ptr<> [Josuttis], [Meyers3].


: - , ( ). ( ) , . std::shared_ptr<> std::weak_ptr<> . . [Josuttis], [Meyers3].


- [Alexandrescu]. ( ) , [Schildt]. , .


( ) [Alger].


-. [Josuttis] [Alexandrescu].


- .NET Java. , , , .



4.


, C++ rvalue- . C++98 std::auto_ptr<> , , , . , , ( ). C++11 rvalue- , , . C++11 std::auto_ptr><> std::unique_ptr<> . , [Josuttis], [Meyers3].


: - ( std::fstream , etc.), ( std::thread , std::unique_lock<> , etc.). MFC , ( CFile , CEvent , CMutex , etc.).



5. —


. , . , , , . , , , ( ) . , , , . ( ) , . , . — . 6.


, - -, « », - . - . , , , , - . «».



6. -


, - . , -. .



6.1.


- . , , :


  1. . , .
  2. .
  3. .

, , , . C++11 .


« » (resource acquisition is initialization, RAII). RAII ( ), ., [Dewhurst1]. «» RAII. , , , (immutable) RAII.



6.2.


, RAII, , , . - , , - . , , , . .



6.2.1.


, , , , :


  1. , .
  2. .
  3. .
  4. .

C++11 , , , . , - clear() , , , . . , shrink_to_fit() , , (. ).


, RAII, , , . , .


 class X { public: // RAII    X(const X&) = delete;            //      X& operator=(const X&) = delete; //      X(/*  */);              //      ~X();                            //   //     X() noexcept;                    //       X(X&& src) noexcept              //      X& operator=(X&& src) noexcept;  //    // ... }; 

.


 X x;                    //  ""  x = X(/*  */); //   x = X(/*  */); //   ,   x = X();                //   

std::thread .


2.4, - . , - - . .


 class X { // RAII // ... public: // ,         X() noexcept;    X(X&& src) noexcept;    X& operator=(X&& src) noexcept;    void Swap(X& other) noexcept; //      void Create(/*  */); //      void Close() noexcept;        //   // ... }; X::X() noexcept {/*    */} 

:


 X::X(X&& src) noexcept : X() {    Swap(src); } X& X::operator=(X&& src) noexcept {    X tmp(std::move(src)); //     Swap(tmp);    return *this; } 

- :


 void X::Create(/*  */) {    X tmp(/*  */); //      Swap(tmp); } void X::Close() noexcept {    X tmp;    Swap(tmp); } 

, , , - . , , , . , .


- « », , . : , , ( ). : , . , : , , . , . [Sutter], [Sutter/Alexandrescu], [Meyers2].


, RAII .



6.2.2.


RAII . , , , , :


  1. , .
  2. .
  3. . , .
  4. .
  5. .

«» RAII, — . , , . 3. . «», .



6.2.3.


— . RAII , . , . , , ( -). - ( -). 6.2.1, .



6.3.


, - RAII, : . , , .



7.


, , , , . - -.


4 -:


  1. .
  2. .
  3. .
  4. .

. , - : , , - .


, . , , -, , .


- . . , (. 6.2.3). , (. 6.2.1). , . , , . , std::shared_ptr<> .



Les applications



. Rvalue-


Rvalue- C++ , , rvalue-. rvalue- T T&& .


:


 class Int {    int m_Value; public:    Int(int val) : m_Value(val) {}    int Get() const { return m_Value; }    void Set(int val) { m_Value = val; } }; 

, rvalue- .


 Int&& r0; // error C2530: 'r0' : references must be initialized 

rvalue- ++ , lvalue. Un exemple:


 Int i(7); Int&& r1 = i; // error C2440: 'initializing' : cannot convert from 'Int' to 'Int &&' 

rvalue:


 Int&& r2 = Int(42); // OK Int&& r3 = 5;       // OK 

lvalue rvalue-:


 Int&& r4 = static_cast<Int&&>(i); // OK 

rvalue- ( ) std::move() , ( <utility> ).


Rvalue rvalue , .


 int&& r5 = 2 * 2; // OK int& r6 = 2 * 2;  // error 

rvalue- .


 Int&& r = 7; std::cout << r.Get() << '\n'; // : 7 r.Set(19); std::cout << r.Get() << '\n'; // : 19 

Rvalue- .


 Int&& r = 5; Int& x = r;           // OK const Int& cx = r;    // OK 

Rvalue- , . , rvalue-, rvalue .


 void Foo(Int&&); Int i(7); Foo(i);            // error, lvalue  Foo(std::move(i)); // OK Foo(Int(4));       // OK Foo(5);            // OK 

, rvalue rvalue- , . rvalue-.


, , , rvalue-, (ambiguous) rvalue .



 void Foo(Int&&); void Foo(const Int&); 


 Int i(7); Foo(i);            // Foo(const Int&) Foo(std::move(i)); // Foo(Int&&) Foo(Int(6));       // Foo(Int&&) Foo(9);            // Foo(Int&&) 

: rvalue- lvalue.


 Int&& r = 7; Foo(r);            // Foo(const Int&) Foo(std::move(r)); // Foo(Int&&) 

, rvalue-, lvalue std::move() . . 2.4.


++11, rvalue- — -. (lvalue/rvalue) this .


 class X { public:    X();    void DoIt() &;  // this   lvalue    void DoIt() &&; // this   rvalue // ... }; X x; x.DoIt();   // DoIt() & X().DoIt(); // DoIt() && 


.


, ( std::string , std::vector<> , etc.) . — . , rvalue- . , , - , - , . , , , rvalue, lvalue. , rvalue. . , ( lvalue), RVO.



Les références


[Alexandrescu]
, . C++.: . de l'anglais - M.: LLC «I.D. », 2002.


[Guntheroth]
, . C++. .: . de l'anglais — .: «-», 2017.


[Josuttis]
, . C++: , 2- .: . de l'anglais - M.: LLC «I.D. », 2014.


[Dewhurst1]
, . C++. , 2- .: . de l'anglais — .: -, 2013.


[Dewhurst2]
, . C++. .: . de l'anglais — .: , 2012.


[Meyers1]
, . C++. 35 .: . de l'anglais — .: , 2000.


[Meyers2]
, . C++. 55 .: . de l'anglais — .: , 2014.


[Meyers3]
, . C++: 42 C++11 C ++14.: . de l'anglais - M.: LLC «I.D. », 2016.


[Sutter]
, . C++.: . de l'anglais — : «.. », 2015.


[Sutter/Alexandrescu]
, . , . ++.: . de l'anglais - M.: LLC «I.D. », 2015.


[Schildt]
, . C++.: . de l'anglais — .: -, 2005.


[Alger]
, . C++: .: . de l'anglais — .: « «», 1999.




Source: https://habr.com/ru/post/fr425837/


All Articles