2017-05-16 4 views
1

Les valeurs R semblent fournir un support incomplet pour les temporaires non nommés, ou est-ce que quelque chose me manque?Les valeurs R semblent fournir un support incomplet pour les temporaires sans nom, ou est-ce que quelque chose me manque?


11 C++ fournit un excellent support pour rvalues ​​à mettre en œuvre la sémantique de déplacement, utiles pour convertir coûteux et copier des cycles affecter en mouvements rapides et bon marché à temps constant, semblables à moving une référence. Mais C++ 11 est arrivé tard dans le jeu, et à ce moment-là, j'avais une solution complète au problème temporaire coûteux, sans nom, en utilisant la solution basée sur les classes décrite ci-dessous. Ce n'est que lorsque j'ai récemment tenté de remplacer ma solution par des constructeurs de mouvement C++ 11 «modernes», que j'ai découvert que la gestion de rvalue ne couvre pas les cas importants couverts par la solution basée sur les classes. Un exemple représentatif est l'expression A + B. Lorsque A est un temporaire non nommé (rvalue), une implémentation sur place de A + = B est appropriée, quand A n'est pas un temporaire sans nom (lvalue), A + B calcule un nouveau résultat. Mais le support de C + 11 rvalue semble n'adresser qu'un bon argument, mais pas un rvalue de gauche.

Par extension, cette limitation affecte tous les autres opérateurs et fonctions du type de base qui pourraient bénéficier de traiter * ceci comme valeur de référence le cas échéant. Notez que A + B peut même être calculé comme B + = A, quand A est une lvalue et B est une rvalue. Les avantages d'une solution complète peuvent souvent être appliqués plus à ces valeurs qu'aux bons arguments. Si C++ 11 ne fournit qu'une demi-solution ici, la solution basée sur les classes ci-dessous reste significativement supérieure pour beaucoup de choses. Est-ce que j'ai râté quelque chose?


Donc, nous allons déduire une classe T temporaire sans nom de la classe des valeurs S, ajouter des constructeurs appropriés, les affectations, les opérateurs et les fonctions de S et T, puis remplacer T S comme type de retour pour toutes les fonctions et les opérateurs qui retourne un résultat S. Avec cela, nous obtenons tous la même sémantique de mouvement qu'avec des valeurs, ainsi que la prise en charge de fonctions et d'opérateurs supplémentaires qui peuvent fonctionner plus rapidement sur des valeurs temporaires non nommées sur place.

class S {      // S is a sample base type to extend 
protected: 
    mutable char* p;   // mutable pointer to storage 
    mutable int length;  // mutable current length 
    mutable int size;   // mutable current size 
public: 
    ~S ();     // S destructor 
    S (char* s);    // construct from data 
    S (const S& s);   // from another S 
    S (const T& s);   // construct from an unnamed temporary 

    T& result () { return (T&)*this; } // cast *this into a T& (an equivalent to std::move (*this)) 
    S& take (S& s);      // free *this, move s to *this, put s in empty/valid state 

    S& operator= (const S& s);   // copy s to *this 
    S& operator= (const T& s);   // assign from unnamed temporary using take () 

    S& operator+= (const S& v);   // add v to *this in-place 
    S& operator-= (const S& v);   // subtract v from *this in-place 
    S& operator<<= (Integer shift);  // shift *this in-place 
    S& operator>>= (Integer shift); 

    T operator+ (const S& v);  // add v to *this and return a T 
    T operator- (const S& v);  // subtract v from *this and return a T 
    etc... 
}; 

class T : public S {    // T is an unnamed temporary S 
private: 
    T& operator= (const T& s);   // no public assignments 
    void* operator new (size_t size); // don't define -- no heap allocation 
    void operator delete (void* ptr); 
public: 
    T (char* s) : S (s) { };   // create a new temporary from data 
    T (const S& s) : S (s) { };   // copy a new temporary from a non-temporary 
    T (const T& s) : S (s) { };   // move a temporary to new temporary 

    T operator<< (int shift) const { return ((S&)*this <<= shift).result (); } 
    T operator>> (int shift) const { return ((S&)*this >>= shift).result (); } 

    T operator+ (const S& v) const { return ((S&)*this += v).result (); } 
    T operator- (const S& v) const { return ((S&)*this -= v).result (); } 
}; 

Notez que cette méthode a démontré sa justesse et de l'efficacité à travers une variété de types de données complets (y compris les chaînes, les tableaux, les grands entiers, etc.) depuis 2001, il fonctionne sans aucune référence à 11 C++ caractéristiques et ne repose sur aucune fonctionnalité de langage indéfini.

+7

Vous recherchez * ref-qualifier * s. –

+3

Cette question est très verbeuse et pas particulièrement claire. Pourriez-vous le retravailler pour poser une question simple au sujet de C++ s'il vous plaît. Imaginez que nous n'avons jamais vu votre classe auparavant et que vous ne savez pas pourquoi c'est nécessaire. (indice: ce n'est probablement pas le cas) –

+1

Comment utilisez-vous 'T'? Avez-vous besoin de garder une trace de la variable temporaire et d'effectuer des copies pour convertir les non-temporaires en temporaires? – nwp

Répondre

2

Vous semblez avoir tort dans vos hypothèses. C++ supporte les valeurs sur la gauche ou la droite.

Il y a deux façons de le faire.

struct noisy { 
    noisy() { std::cout << "ctor()\n"; }; 
    noisy(noisy const&) { std::cout << "ctor(const&)\n"; }; 
    noisy(noisy &&) { std::cout << "ctor(&&)\n"; }; 
    noisy& operator=(noisy const&) { std::cout << "asgn(const&)\n"; return *this; }; 
    noisy& operator=(noisy &&) { std::cout << "asgn(&&)\n"; return *this; }; 
    ~noisy() { std::cout << "dtor\n"; }; 
}; 
struct Bob:noisy { 
    int val = 0; 
    Bob(int x=0):val(x) {} 
    Bob(Bob&&)=default; 
    Bob(Bob const&)=default; 
    Bob& operator=(Bob&&)=default; 
    Bob& operator=(Bob const&)=default; 
    friend Bob operator+(Bob lhs, Bob const& rhs) { 
    lhs += rhs; 
    return lhs; 
    } 
    friend Bob& operator+=(Bob& lhs, Bob const& rhs) { 
    lhs.val += rhs.val; 
    return lhs; 
    } 
    friend Bob operator+=(Bob&& lhs, Bob const& rhs) { 
    lhs += rhs; // uses & overload above 
    return std::move(lhs); 
    } 
}; 

Bob utilise les opérateurs d'amis pour faire essentiellement ce que vous voulez.

Ceci est ma solution préférée, les opérateurs amis sont beaucoup plus symétriques que les opérateurs membres.

struct Alice:noisy { 
    int val = 0; 
    Alice(int x=0):val(x) {} 
    Alice(Alice&&)=default; 
    Alice(Alice const&)=default; 
    Alice& operator=(Alice&&)=default; 
    Alice& operator=(Alice const&)=default; 
    Alice operator+(Alice const& rhs) const& { 
    return Alice(*this) + rhs; 
    } 
    Alice operator+(Alice const& rhs) && { 
    *this += rhs; 
    return std::move(*this); 
    } 
    Alice& operator+=(Alice const& rhs)& { 
    val += rhs.val; 
    return *this; 
    } 
    Alice operator+=(Alice const& rhs)&& { 
    *this += rhs; // uses & overload above 
    return std::move(*this); 
    } 
}; 

Alice utilise les fonctions membres à faire de même. Il a les problèmes habituels avec les fonctions membres sur les opérateurs amis.

Notez l'utilisation de & et && et const&après les arguments de la fonction membre. Ceci est connu sous le nom de "référence rvalue à *this" dans une discussion informelle.Il vous permet de choisir quelle surcharge basée sur la valeur r/l de l'objet avec lequel vous travaillez.

Code d'essai:

Bob bob; 
Bob b2 = Bob{3}+bob; 

Alice alice; 
Alice a2 = Alice{3}+alice; 

Live example.

Dans les deux cas, aucun objet n'est copié.

Notez que j'ai supposé que l'addition était asymétrique (malgré l'utilisation d'un int pour l'état). Si c'était le cas, vous pouvez faire une autre efficacité où le lhs est un non-rvalue tandis que le rhs est.

+0

Votre solution est également basée sur des qualificatifs ref, un concept que je n'avais jamais vu auparavant, et qui n'apparaissait pas avant les compilateurs très récents (VC++ 2015). Même maintenant, en utilisant les nouvelles références à * ceci empêche la compilation avec tous les compilateurs, sauf les plus récents. –

+0

@ChrisCochran Il s'agit d'une fonctionnalité C++ 11. Cela répond directement à votre question ("est-ce que je me trompe?"), Et résout votre problème, tout en faisant correspondre les balises de votre question, etc Si vous voulez vous plaindre de l'implémentation lente des nouvelles fonctionnalités C++ dans MSVC, c'est un autre sujet. – Yakk