2016-12-30 1 views
1

L'utilisation de std::list prenant en charge la sémantique de déplacement à titre d'exemple.Réduction de l'affectation d'un objet temporaire à la construction en place

std::list<std::string> X; 
... //X is used in various ways 
X=std::list<std::string>({"foo","bar","dead","beef"}); 

La façon la plus simple pour le compilateur de faire l'affectation depuis C++ 11 est:

  1. destroy X
  2. construire std::list
  3. mouvement std::list à X

Maintenant, le compilateur n'est pas autorisé à faire f uite à la place:

  1. destroy X
  2. contruct std::list en place

car si cela permet d'économiser évidemment une autre memcpy il élimine la cession. Quel est le moyen pratique de rendre un deuxième comportement possible et utilisable? Est-il prévu dans les futures versions de C++?

Je pense que C++ ne permet toujours pas, sauf avec l'écriture:

X.~X(); 
new(&X) std::list<std::string>({"foo","bar","dead","beef"}); 

Ai-je raison?

+1

Pour ceux qui ne savent pas ce que QList fait, pourquoi ne pas utiliser std :: list comme exemple. –

+1

@latedeveloper: Parce que 'QList' est un tableau de pointeurs, alors que' std :: list' est une liste chaînée. Ce sont des types très différents, avec des propriétés d'allocation différentes. –

+0

@NicolBolas: 'QList' est utilisé de façon purement démostrative, ses propriétés importent peu. Leur point est valide. –

Répondre

2

Vous pouvez réellement le faire en définissant operator = pour prendre une liste d'initialisation. Pour std :: liste, il suffit d'appeler

X = {"foo","bar","dead","beef"}. 

Dans votre cas, ce qui se passait est en réalité:

  1. construire un
  2. temporaire
  3. appel opérateur d'affectation de déplacement sur X avec le
temporaire

Sur la plupart des objets, tels que std :: list, cela ne sera pas vraiment cher par rapport à la simple construction d'un objet.

Cependant, il y a toujours des allocations supplémentaires pour le stockage interne de la deuxième std :: list, ce qui pourrait être évité: nous pourrions réutiliser le stockage interne déjà alloué pour X si possible. Ce qui est happenning est:

  1. Construct: le temporaire alloue un espace pour les éléments
  2. Déplacer: le pointeur est déplacé vers X; l'espace utilisé par X avant est libéré

Certains objets surcharge l'opérateur d'affectation à prendre une liste d'initialiseur, et il est le cas pour std::vector et std::list. Un tel opérateur peut utiliser le stockage déjà attribué en interne, ce qui est la solution la plus efficace ici.

// S'il vous plaît insérez le décousu d'habitude sur l'optimisation prématurée ici

+0

J'ai remplacé 'QList' par' std :: list' parce que cela n'avait pas vraiment d'importance pour moi. –

+0

Merci; L'opérateur d'affectation de liste d'initialisation fonctionne avec std :: list; Je vais éditer –

+0

"S'il vous plaît insérez l'habituelle randonnée sur l'optimisation prématurée ici" - C++ est un langage axé sur les performances, il est toujours intéressant de voir comment il minimise les frais généraux. –

1

Est-il prévu dans les futures versions de C++?

Non. Et Dieu merci pour cela.

L'affectation est et non identique à détruire-puis-créer. X n'est pas détruit dans votre exemple d'affectation. X est un objet vivant; le contenu de X peut être détruit, mais X lui-même ne l'est jamais. Et ne devrait pas être.

Si vous voulez détruire X, alors vous avez cette capacité, en utilisant explicit-destructor-and-placement-new. Bien que grâce à la possibilité de membres const, vous aurez également besoin de blanchir le pointeur sur l'objet si vous voulez être en sécurité. Mais l'affectation ne doit jamais être considérée comme équivalente à .

Si l'efficacité est votre préoccupation, il est préférable d'utiliser la fonction membre assign. En utilisant assign, le X a la possibilité de réutiliser les allocations existantes. Et cela le rendrait presque certainement plus rapide que votre version "destroy-plus-construct". Le coût de déplacement d'une liste liée dans un autre objet est trivial; le coût d'avoir à détruire toutes ces allocations uniquement pour les allouer à nouveau ne l'est pas.

Ceci est particulièrement important pour std::list, car il a un lot d'allocations.

Dans le pire des cas, assign ne sera pas moins efficace que tout ce que vous pourriez trouver hors de la classe. Et dans le meilleur des cas, ce sera beaucoup mieux.

+0

J'ai ajouté une ligne 'et écrit une fonction pour construire l'objet de retour en place sans même destruction manuelle et les pointeurs bruts sont encore plus impossibles. 'À la fin de la question. Pourriez-vous clarifier cela aussi? –

+0

Cela pourrait être une grande partie de la question originale si ... –

1

Lorsque vous avez une déclaration impliquant une cession de déplacement:

x = std::move(y); 

Le destructor n'est pas appelé à x avant de faire le déménagement. Cependant, après le déménagement, le destructeur sera appelé à y. L'idée derrière un opérateur d'affectation de mouvement est qu'il pourrait être en mesure de déplacer le contenu de y à de manière simple (par exemple, en copiant un pointeur sur le stockage de y en). Il doit également s'assurer que son contenu précédent est détruit correctement (il peut choisir de l'échanger avec y, car vous savez que y ne peut plus être utilisé, et que le destructeur de y sera appelé). Si l'affectation de déplacement est en ligne, le compilateur peut être en mesure de déduire que toutes les opérations nécessaires pour déplacer le stockage de y vers x sont simplement équivalentes à la construction sur place.

+0

Donc, le constructeur de déplacement devrait réimplémenter une partie du destructeur? –

+0

Je comprends que l'affectation peut être inline mais elle ne sera toujours pas un droit de construction sur place? –

+1

Je parlais de l'opérateur move * assignment *, c'est différent du constructeur * move *. Dans tous les cas, l'opérateur d'affectation de mouvement peut soit détruire lui-même le LHS, soit choisir de l'échanger avec le RHS, car il peut compter sur le destructeur appelé sur le RHS. –

1

Re votre dernière question

Ai-je raison?

No.

Vos idées sur ce qui est autorisé ou non sont fausses. Le compilateur est autorisé à substituer toute optimisation tant qu'il préserve les effets observables. C'est ce qu'on appelle la règle "comme si". Les optimisations possibles incluent la suppression de tout ce code, si cela n'affecte rien d'observable. En particulier, votre "n'est pas autorisé" pour le deuxième exemple est complètement faux, et le raisonnement "il élimine l'assignation" s'applique également à votre premier exemple, où vous tirerez la conclusion inverse, c'est-à-dire une auto-contradiction.