2010-04-24 11 views
3

Je suis en train de réaliser l'optimisation suivante dans ma bibliothèque de conteneurs:élément conteneur inséré si possible

  • lors de l'insertion d'un élément référencé lvalue, le copier dans la mémoire interne;
  • mais lors de l'insertion de l'élément référencé rvalue, déplacez-le s'il est pris en charge.

L'optimisation est censée être utile, par ex. si le type d'élément contenu est quelque chose comme std::vector, où le déplacement si possible donnerait une accélération substantielle.

Cependant, jusqu'à présent, je n'ai pas réussi à concevoir de plan de travail pour cela. Mon conteneur est assez compliqué, donc je ne peux pas simplement dupliquer le code insert() plusieurs fois: il est grand. Je veux garder tout le "vrai" code dans une certaine aide interne, disons do_insert() (peut être modélisé) et diverses fonctions-like insert() appellent simplement cela avec des arguments différents.

Mon meilleur code pari pour ce (un prototype, bien sûr, sans rien faire réel):

#include <iostream> 
#include <utility> 

struct element 
{ 
    element() { }; 
    element (element&&) { std::cerr << "moving\n"; } 
}; 

struct container 
{ 
    void insert (const element& value) 
    { do_insert (value); } 

    void insert (element&& value) 
    { do_insert (std::move (value)); } 

private: 
    template <typename Arg> 
    void do_insert (Arg arg) 
    { element x (arg); } 
}; 

int 
main() 
{ 
    { 
    // Shouldn't move. 
    container c; 
    element x; 
    c.insert (x); 
    } 

    { 
    // Should move. 
    container c; 
    c.insert (element()); 
    } 
} 

Cependant, cela ne fonctionne pas au moins avec GCC 4.4 et 4.5: il ne imprime pas « bouger "sur stderr. Ou est ce que je veux impossible à réaliser et c'est pourquoi les fonctions comme emplace() existent en premier lieu?

Répondre

2

Je pense que vous pourriez avoir besoin de transmettre l'argument:

template <typename Arg> 
    void do_insert (Arg&& arg) 
    { element x (std::forward<Arg>(arg)); } 

code complet:

#include <iostream> 
#include <utility> 

struct element 
{ 
    element() { }; 
    element (const element&) { std::cerr << "copying\n"; } 
    element (element&&) { std::cerr << "moving\n"; } 
}; 

struct container 
{ 
    void insert (const element& value) 
    { do_insert (value); } 

    void insert (element&& value) 
    { do_insert (std::move(value)); } 

private: 
    template <typename Arg> 
    void do_insert (Arg&& arg) 
    { element x (std::forward<Arg>(arg)); } 
}; 

int 
main() 
{ 
    { 
    // Shouldn't move. 
    container c; 
    element x; 
    c.insert (x); 
    } 
    { 
    // Should move. 
    container c; 
    c.insert (element()); 
    } 
} 

Le mot-clé que vous pourriez rechercher est "le transfert parfait".

+0

Si je fais cela, je reçois deux "en mouvement" sur la sortie. Mais je suppose que j'ai besoin de lire de la documentation sur cette fonction. – doublep

+0

Après avoir édité le code fonctionne et manque les arguments explicites étranges <> à 'do_insert()', contrairement à ma propre réponse. Merci! – doublep

0

Je vous recommande de copier la façon dont cela se fait dans votre implémentation STL (ou GNU, qui devrait être capable de lire en ligne quand même).

Mais

template <typename Arg> 
    void do_insert (Arg arg) 
    { element x (move(arg)); } 

pourrait faire l'affaire.

Cette fonctionnalité est distincte de emplace et vous avez raison de dire que cela fonctionne dans la bibliothèque standard.

EDIT j'ai fait quelques changements et a découvert

  • Lorsque vous obtenez deux messages, il est l'un de chaque insertion
  • La deuxième insertion semble être bien
  • Le premier génère un move parasite mais l'objet original n'a pas été déplacé. Donc concentrez-vous sur la recherche d'un temporaire qui est en train d'être déplacé ... ce n'est pas vraiment une si mauvaise chose.

.

#include <iostream> 
#include <utility> 

struct element 
{ 
    element() : moved(false) { }; 
    element (element&&) { moved = true; std::cerr << "moving\n"; } 
    bool moved; 
}; 

struct container 
{ 
    void insert (const element& value) 
    { do_insert (value); } 

    void insert (element&& value) 
    { do_insert (std::move (value)); } 

private: 
    template <typename Arg> 
    void do_insert (Arg arg) 
    { element x (std::move(arg)); } 
}; 

int 
main() 
{ 
    std::cerr << "try 1\n"; 
    { 
    // Shouldn't move. 
    container c; 
    element x; 
    c.insert (x); 
    std::cerr << x.moved << "\n"; 
    } 

    std::cerr << "try 2\n"; 
    { 
    // Should move. 
    container c; 
    c.insert (element()); 
    } 
} 
+0

Il n'est pas implémenté pour un containerd similaire ('unordered_ *') dans GNU STL. Cela pourrait indiquer qu'il est impossible de le faire (facilement) ou peut-être qu'il n'est pas encore implémenté. A propos de la transmission ne bouge pas: peut-être, mais cela n'aide pas non plus. – doublep

+1

@doublep: Mon point est en fait que vous devez modifier 'do_insert'. GNU (et probablement tout le monde) gère cette étape avec l'allocateur, donc cela ne devrait pas importer quel conteneur. Je ne suis pas sûr à 100% s'il faut utiliser move ou forward, mais l'absence de l'un ou l'autre dans votre 'do_insert' est clairement un bug. – Potatoswatter

+0

@Potatoswatter: Peu importe ce que j'ajoute dans 'do_insert()', j'obtiens deux messages "en mouvement". C'est à dire. ça pourrait bien être un bug, mais je n'ai aucune idée de comment le résoudre. – doublep

0

Je ne peux pas dire que je comprends pourquoi cela fonctionne et un autre code ne pas, mais cela semble faire l'affaire (créé grâce à des notes de Potatoswatter):

#include <iostream> 
#include <utility> 

struct element 
{ 
    element() { }; 
    element (const element&) { std::cerr << "copying\n"; } 
    element (element&&) { std::cerr << "moving\n"; } 
}; 

struct container 
{ 
    void insert (const element& value) 
    { do_insert <const element&> (value); } 

    void insert (element&& value) 
    { do_insert <element&&> (std::forward <element&&> (value)); } 

private: 
    template <typename Arg> 
    void do_insert (Arg arg) 
    { element x (std::forward <Arg> (arg)); } 
}; 

int 
main() 
{ 
    std::cerr << "1\n"; 
    { 
    // Shouldn't move. 
    container c; 
    element x; 
    c.insert (x); 
    } 

    std::cerr << "2\n"; 
    { 
    // Should move. 
    container c; 
    c.insert (element()); 
    } 
} 

Je reçois la sortie suivante avec GCC 4.4 et 4.5:

1 
copying 
2 
moving