Comment autoriser la construction de copie elision pour les classes c++ (pas seulement les structures POD C)

considérons le code suivant:

#include <iostream>
#include <type_traits>

struct A
{
  A() {}
  A(const A&) { std::cout << "Copy" << std::endl; }
  A(A&&) { std::cout << "Move" << std::endl; }
};

template <class T>
struct B
{
  T x;
};

#define MAKE_B(x) B<decltype(x)>{ x }

template <class T>
B<T> make_b(T&& x)
{
  return B<T> { std::forward<T>(x) };
}

int main()
{
  std::cout << "Macro make b" << std::endl;
  auto b1 = MAKE_B( A() );
  std::cout << "Non-macro make b" << std::endl;
  auto b2 = make_b( A() );
}

il en résulte ce qui suit:

"151950920 Macro" b

Non macro b

Move

noter que le b1 est construit sans déplacement, mais que la construction du b2 nécessite un déplacement.

je dois aussi taper déduction, comme A dans l'utilisation réelle peut être un complexe le type qui est difficile à écrire explicitement. Je dois aussi être capable de nicher des cris (i.e. make_c(make_b(A())) ).

est-ce qu'une telle fonction est possible?

autres réflexions:

N3290 Final c++0x draft page 284:

Cette élision de copier/déplacer des opérations, appelé copie élision, est autorisé dans les circonstances suivantes:

lorsqu'un objet de classe temporaire qui a n'est pas liée à une référence (12.2) serait copié/déplacé à une classe objet avec le même cv-sans réserve tapez, l'opération de copie/déplacement peut être omise en construisant le temporaire objet directement dans la cible du omis de copier/déplacer

malheureusement cela semble que nous ne pouvons pas elide des copies (et des mouvements) des paramètres de fonction pour les résultats de fonction (y compris constructors), ces temporairesétant soit liés à une référence (lorsqu'ils sont passés par référence), soit plus temporels (lorsqu'ils sont passés par valeur). Il semble que la seule façon d'éluder toutes les copies lors de la création d'un objet composite est de créer un agrégat. Cependant, les agrégats ont certaines restrictions, comme l'exigence que tous les membres soient publics, et aucun constructeur défini par l'utilisateur.

je ne pense pas que cela a du sens pour le C++ pour permettre des optimisations pour POD C-les structures d'agrégation construction mais ne permettent pas les mêmes optimisations pour la construction de Classe C++ non-POD.

Est-il un moyen pour permettre de copier/déplacer des élision pour les non-agrégats de construction?

ma réponse:

cette construction permet aux copies d'être elided pour les types non-POD. J'ai eu cette idée de la réponse de David Rodríguez ci-dessous. Il nécessite C++11 lambdas. Dans cet exemple ci-dessous j'ai changé make_b prendre deux arguments pour rendre les choses moins triviales. Il n'y a aucun appel à aucun mouvement ou copier des constructeurs.

#include <iostream>
#include <type_traits>

struct A
{
  A() {}
  A(const A&) { std::cout << "Copy" << std::endl; }
  A(A&&) { std::cout << "Move" << std::endl; }
};

template <class T>
class B
{
public:
  template <class LAMBDA1, class LAMBDA2>
  B(const LAMBDA1& f1, const LAMBDA2& f2) : x1(f1()), x2(f2()) 
  { 
    std::cout 
    << "I'm a non-trivial, therefore not a POD.n" 
    << "I also have private data members, so definitely not a POD!n";
  }
private:
  T x1;
  T x2;
};

#define DELAY(x) [&]{ return x; }

#define MAKE_B(x1, x2) make_b(DELAY(x1), DELAY(x2))

template <class LAMBDA1, class LAMBDA2>
auto make_b(const LAMBDA1& f1, const LAMBDA2& f2) -> B<decltype(f1())>
{
  return B<decltype(f1())>( f1, f2 );
}

int main()
{
  auto b1 = MAKE_B( A(), A() );
}

si quelqu'un sait comment y arriver, je serais très intéressé de le voir.

précédente discussion:

Cela fait quelque peu suite aux réponses aux questions suivantes:

Peut-création d'objets composites à partir de les temporairespeuvent-ils être optimisés?

éviter le besoin de # définir avec des modèles d'expression

élimination des copies inutiles lors de la construction d'objets composites

27
demandé sur Community 2011-05-04 06:11:23

4 réponses

comme Anthony L'a déjà mentionné, le standard interdit copie elision de l'argument d'une fonction au retour de la même fonction. La logique qui sous-tend cette décision est que copy elision (and move elision) est une optimisation par laquelle deux objets du programme sont fusionnés dans le même emplacement mémoire, c'est-à-dire que la copie est élidée par le fait que les deux objets sont un. La citation type (partielle) ci-dessous est suivie d'un ensemble de circonstances dans lesquelles la copie est autorisée., qui ne comprennent pas ce cas particulier.

qu'est-ce qui différencie ce cas particulier? La différence est essentiellement que le fait qu'il y ait un appel de fonction entre l'objet original et les objets copiés, et l'appel de fonction implique qu'il y a des contraintes supplémentaires à considérer, en particulier la convention d'appel.

avec une fonction T foo( T ) , et un utilisateur appelant T x = foo( T(param) ); , dans le cas général, avec une compilation séparée, le compilateur va créer un objet $tmp1 dans l'emplacement que la convention d'appel exige que le premier argument soit. Il appellera alors la fonction et initialisera x à partir de la déclaration de retour. Voici la première occasion pour la copie elision: en plaçant soigneusement x sur l'emplacement où le temporaire retourné est, x et l'objet retourné de foo devenir un seul objet, et cette copie est elided. So far So good. Le problème est que l'appel la convention en général n'aura pas l'objet retourné et le paramètre dans le même emplacement, et pour cette raison, $tmp1 et x ne peuvent pas être un emplacement unique dans la mémoire.

sans voir la définition de la fonction, le compilateur ne peut pas savoir que le seul but de l'argument de la fonction est de servir d'instruction de retour, et en tant que tel, il ne peut pas supprimer cette copie supplémentaire. On peut faire valoir que si la fonction est inline alors le compilateur aurait l'information supplémentaire manquante pour comprendre que le temporaire utilisé pour appeler la fonction, la valeur retournée et x sont un seul objet. Le problème est que cette copie particulière ne peut être supprimée que si le code est effectivement inlined (pas seulement s'il est marqué inline mais réellement inlined ) si un appel de fonction est requis, alors la copie ne peut pas être supprimée. Si la norme permettait que cette copie soit supprimée lorsque le code est inlined, cela impliquerait que le comportement d'un programme serait différent en raison du code du compilateur et non de l'utilisateur --le mot-clé inline ne force pas l'inclinaison, cela signifie seulement que les définitions multiples de la même fonction ne représentent pas une violation de L'ODR.

notez que si la variable était créé à l'intérieur de la fonction (par rapport à passé en elle) comme dans: T foo() { T tmp; ...; return tmp; } T x = foo(); alors les deux copies peuvent être elided: il n'y a aucune restriction à partir de l'endroit où tmp doit être créé (ce n'est pas un paramètre d'entrée ou de sortie de la fonction de sorte que le compilateur est en mesure de le déplacer n'importe où, y compris l'emplacement du type retourné, et du côté appelant, x peut comme dans l'exemple précédent être soigneusement localisé dans l'emplacement de cette même déclaration de retour, ce qui signifie essentiellement que tmp , la déclaration de retour et x peut être un objet unique.

de votre problème particulier, si vous recourez à une macro, le code inline, il n'y a pas de restrictions sur les objets et la copie peut être gommés. Mais si vous ajoutez une fonction, vous ne pouvez pas éluder la copie de l'argument de l'instruction return. Donc, juste l'éviter. Au lieu d'utiliser un modèle de déplacer l'objet, créer un modèle de construire un objet:

template <typename T, typename... Args>
T create( Args... x ) {
   return T( x... );
}

et cette copie peut être effacée par le compilateur.

notez que je n'ai pas traité de la construction de déménagement, comme vous semblez préoccupé par le coût de même la construction de déménagement, même si je crois que vous aboyez à la mauvaise arbre. Compte tenu d'une motivation réel cas d'utilisation, je suis tout à fait sûr que les gens ici vont venir avec un couple d'idées efficaces.

12.8 / 31

lorsque certains critères sont respectés, une implémentation est autorisée à omettre la construction de copie/déplacement d'un objet de classe, même si le constructeur de copie / déplacement et / ou destructeur pour l'objet ont des effets secondaires. Dans de tels cas, l'implémentation traite la source et la cible de l'opération de copie/déplacement omise comme simplement deux façons différentes de se référer au même objet, et la destruction de cet objet se produit à la dernière des périodes où les deux objets auraient été détruits sans l'optimisation.

8
répondu David Rodríguez - dribeas 2011-05-20 23:16:11

... mais la construction de b2 nécessite un déplacement.

Non. Le compilateur est autorisé à elide le mouvement; si cela se produit est spécifique à la mise en œuvre, en fonction de plusieurs facteurs. Il est également autorisé à se déplacer, mais il ne peut pas copier (le déplacement doit être utilisé au lieu de copier dans cette situation).

Il est vrai que vous n'êtes pas garanti que le mouvement sera être gommés. Si vous doit être garanti qu'aucun mouvement ne se produira, puis soit utiliser la macro ou enquêter sur les options de votre mise en œuvre pour contrôler ce comportement, en particulier la fonction inlining.

5
répondu Fred Nurk 2011-05-04 02:37:16

vous ne pouvez pas optimiser la copie/déplacement de l'objet A du paramètre make_b au membre de l'objet B créé.

cependant, c'est le point entier de la sémantique de déplacement --- en fournissant une opération de déplacement léger pour A vous pouvez éviter une copie potentiellement coûteuse. par exemple , si A était en fait std::vector<int> , alors la copie du contenu du vecteur peut être évitée en utilisant le constructeur move, et au lieu juste le ménage pointeurs seront transférés.

4
répondu Anthony Williams 2011-05-06 10:14:29

Ce n'est pas un gros problème. Il suffit de modifier légèrement la structure du code.

au lieu de:

B<A> create(A &&a) { ... }
int main() { auto b = create(A()); }

Vous pouvez toujours le faire:

int main() { A a; B<A> b(a); ... }

si le constructeur de B est comme ceci, alors il ne prendra pas de copies:

template<class T>
class B { B(T &t) :t(t) { } T &t; };

Le boîtier composite sera trop de travail:

struct C { A a; B b; };
void init(C &c) { c.a = 10; c.b = 20; }
int main() { C c; init(c); } 

et il n'a même pas besoin de fonctionnalités c++0x pour le faire.

1
répondu tp1 2011-05-07 18:23:25