49 votes

Surcharge sur la référence, par opposition à la valeur de passage unique + std :: move?

Il semble que le principal conseil concernant C++0x du rvalues est d'ajouter des constructeurs de déplacement et de déplacer les opérateurs à vos classes, jusqu'à ce que les compilateurs de défaut de les mettre en œuvre.

Mais l'attente est une stratégie perdante si vous utilisez VC10, parce que la génération automatique ne sera probablement pas ici jusqu'à ce que VC10 SP1, ou dans le pire des cas, VC11. Probablement, l'attente va être mesurée en années.

Ici se trouve mon problème. Écrire tout ce code en double n'est pas amusant. Et c'est désagréable à regarder. Mais c'est un fardeau bien reçu, pour ces classes réputée lente. Pas tellement pour les centaines, sinon des milliers, des classes plus petites.

::soupire:: C++0x était censé me laisser écrire moins de code, pas plus!

Et puis j'ai eu une pensée. Partagé par beaucoup de gens, je suppose.

Pourquoi ne pas simplement passer le tout par la valeur? Ne std::move + copie élision de faire de cette presque optimale?

Exemple 1 - typiquement Pré-0x constructeur

OurClass::OurClass(const SomeClass& obj) : obj(obj) {}

SomeClass o;
OurClass(o);            // single copy
OurClass(std::move(o)); // single copy
OurClass(SomeClass());  // single copy

Inconvénients: Un gaspillage de copie pour les rvalues.

Exemple 2 - C++0x?

OurClass::OurClass(const SomeClass& obj) : obj(obj) {}
OurClass::OurClass(SomeClass&& obj) : obj(std::move(obj)) {}

SomeClass o;
OurClass(o);            // single copy
OurClass(std::move(o)); // zero copies, one move
OurClass(SomeClass());  // zero copies, one move

Avantages: sans doute le plus rapide.
Inconvénients: Beaucoup de code!

Exemple 3 - Passe-par-valeur + std::move

OurClass::OurClass(SomeClass obj) : obj(std::move(obj)) {}

SomeClass o;
OurClass(o);            // single copy, one move
OurClass(std::move(o)); // zero copies, two moves
OurClass(SomeClass());  // zero copies, one move

Avantages: Pas de code supplémentaire.
Inconvénients: Un gaspillage de se déplacer dans les cas 1 et 2. Les performances en souffriront grandement si SomeClass n'a pas de constructeur de déplacement.


Qu'en pensez-vous? Est-ce correct? Est la charge de se déplacer, généralement, une perte acceptable lorsque comparée à l'avantage de code de réduction?

8voto

ULysses Points 764

J'ai été intéressé par votre question parce que j'étais nouveau le sujet et fait quelques recherches. Permettez-moi de présenter les résultats.

Tout d'abord, votre soupir.

enter code here::soupire:: C++0x était censé me laisser écrire moins de code, pas plus!

ce qu'il est aussi censé vous donner un meilleur contrôle sur le code. Et qu'il n'. Je m'en tiendrais à un constructeur supplémentaire:

OurClass::OurClass(SomeClass&& obj) : obj(std::move(obj)) {}

Personnellement, je préfère de verbosité complexes et des situations importantes, parce que ça me tient et lecteurs de mon code alerté.

prenez, par exemple, le style C cast (T*)pT et le standard C++ static_cast<T*>(pT) Beaucoup plus détaillé, mais un grand pas en avant.

Deuxièmement, j'ai été un peu méfiant au sujet de votre Exemple 3, dernier cas de test. Je pensais qu'il pouvait être un autre constructeur de déplacement impliqué pour créer le passé-par-valeur de paramètre à partir de la rvalue. J'ai donc créé quelques projet rapide dans mon nouveau VS2010 et a obtenu quelques précisions. Je vais poster le code ici, ainsi que les résultats.

la source:

// test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <utility>
#include <iostream>

class SomeClass{
    mutable int *pVal;
public:
    int Val() const { return *pVal; };
    SomeClass(int val){
        pVal = new int(val);
        std::cout << "SomeClass constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }
    SomeClass(const SomeClass& r){
        pVal = new int(r.Val());
        std::cout << "SomeClass copy constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }   
    SomeClass(const SomeClass&& r){
        pVal = r.pVal;
        r.pVal = 0;
        std::cout << "SomeClass move constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }
    ~SomeClass(){
        if(pVal)
            delete pVal;
        std::cout << "SomeClass destructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }
};

class OtherClass{
    SomeClass sc;
public:
    OtherClass(int val):sc(val){
    }

Remarque cette section:

#if 1
    OtherClass(SomeClass r):sc(std::move(r)){
    }
#else
    OtherClass(const SomeClass& r):sc(r){
    }   
    OtherClass(const SomeClass&& r):sc(std::move(r)){
    }
#endif

...

    int Val(){ return sc.Val(); }
    ~OtherClass(){
    }
};

#define ECHO(expr)  std::cout << std::endl << "line " << __LINE__ << ":\t" #expr ":" << std::endl; expr

int _tmain(int argc, _TCHAR* argv[])
{
    volatile int __dummy = 0;
    ECHO(SomeClass o(10));

    ECHO(OtherClass oo1(o));            
    __dummy += oo1.Val();
    ECHO(OtherClass oo2(std::move(o))); 
    __dummy += oo2.Val();
    ECHO(OtherClass oo3(SomeClass(20)));  
    __dummy += oo3.Val();

    ECHO(std::cout << __dummy << std::endl);
    ECHO(return 0);
}

Comme vous l'avez constaté, il y a un moment de la compilation du commutateur qui me permet de tester les deux approches.

Maintenant, il est temps de voir les résultats.

Les résultats sont mieux visualisés dans le texte, le mode de comparaison, sur la gauche, vous pouvez voir l' #if 1 de la compilation, ce qui signifie que nous avons vérifier la solution proposée, et sur la droite - #if 0, ce qui signifie que nous avons vérifier la "casher" manière décrite dans le c++0x! le texte d'alt

J'ai eu tort de soupçonner le compilateur de faire des choses stupides, il a sauvé le mouvement supplémentaire constructeur dans le troisième cas de test.

Mais pour être honnête, nous avons à rendre compte de deux autres destructeurs d'être appelé dans le projet de solution de contournement, mais c'est pour sûr un inconvénient mineur tenant compte du fait qu'aucune action ne doit être effectuée que si un déplacement a eu lieu sur l'objet à été détruits. C'est toujours bon à savoir.

En tout cas, je ne suis pas quitter le point qu'il est préférable d'en écrire un autre constructeur de la classe wrapper. C'est juste une question de plusieurs lignes, puisque tout le travail fastidieux est déjà fait dans le SomeClass qui a avoir un constructeur de déplacement.

Merci pour un sujet intéressant! En ce qui concerne, Leonid.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X