Comment puis-je vérifier si un constructeur de déplacement est généré implicitement?

J’ai plusieurs classes pour lesquelles je souhaite vérifier si un constructeur de déplacement par défaut est généré. Existe-t-il un moyen de vérifier cela (qu’il s’agisse d’une assertion au moment de la compilation, d’une parsing des fichiers object générés ou de quelque chose d’autre)?


Exemple de motivation:

class MyStruct : public ComplicatedBaseClass { std::vector foo; // possibly huge ComplicatedSubObject bar; }; 

Si un membre d’une base ou un membre d’une classe d’ Complicated...Object ne peut pas être déplacé, MyStruct n’aura pas son constructeur de déplacement implicite, et risque donc de ne pas optimiser le travail de copie de foo , lorsqu’un déplacement peut être effectué. même si foo est mobile.


Je souhaite éviter:

  1. vérifier fastidieusement les conditions de la génération implicite de mouvement ,
  2. Par défaut, de manière explicite et récursive, les fonctions membres spéciales de toutes les classes affectées, leurs bases et leurs membres, juste pour s’assurer qu’un constructeur de déplacement est disponible.

J’ai déjà essayé ce qui suit et ils ne fonctionnent pas:

  1. utilisez explicitement std::move – ceci invoquera le ctor de copie si aucun ctor de move n’est disponible.
  2. use std::is_move_constructible réussira s’il existe un constructeur de copie acceptant const Type& , qui est généré par défaut ( tant que le constructeur de déplacement n’est pas explicitement supprimé, au moins ).
  3. utilisez nm -C pour vérifier la présence de move ctor [voir ci-dessous]. Cependant, une approche alternative est viable [voir la réponse].

J’ai essayé de regarder les symboles générés d’une classe sortingviale comme ceci:

 #include  struct MyStruct { MyStruct(int x) : x(x) {} //MyStruct(const MyStruct& rhs) : x(rhs.x) {} //MyStruct(MyStruct&& rhs) : x(rhs.x) {} int x; }; int main() { MyStruct s1(4); MyStruct s2(s1); MyStruct s3(std::move(s1)); return s1.x+s2.x+s3.x; // make sure nothing is optimized away } 

Les symboles générés ressemblent à ceci:

 $ CXXFLAGS="-std=gnu++11 -O0" make -B x; ./x; echo $?; nm -C x | grep MyStruct | cut -d' ' -f3,4,5 g++ -std=gnu++11 -O0 x.cc -ox 12 .pdata$_ZN8MyStructC1Ei .pdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_ .text$_ZN8MyStructC1Ei .text$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_ .xdata$_ZN8MyStructC1Ei .xdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_ MyStruct::MyStruct(int) std::remove_reference::type&& 

La sortie est la même lorsque je copie explicitement par défaut la copie et que je déplace les ctors (pas de symboles).

Avec ma propre copie et déplacer des facteurs, la sortie ressemble à ceci:

 $ vim x.cc; CXXFLAGS="-std=gnu++11 -O0" make -B x; ./x; echo $?; nm -C x | grep MyStruct | cut -d' ' -f3,4,5 g++ -std=gnu++11 -O0 x.cc -ox 12 .pdata$_ZN8MyStructC1Ei .pdata$_ZN8MyStructC1EOKS_ .pdata$_ZN8MyStructC1ERKS_ .pdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_ .text$_ZN8MyStructC1Ei .text$_ZN8MyStructC1EOKS_ .text$_ZN8MyStructC1ERKS_ .text$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_ .xdata$_ZN8MyStructC1Ei .xdata$_ZN8MyStructC1EOKS_ .xdata$_ZN8MyStructC1ERKS_ .xdata$_ZSt4moveIR8MyStructEONSt16remove_referenceIT_E4typeEOS3_ MyStruct::MyStruct(int) MyStruct::MyStruct(MyStruct&&) MyStruct::MyStruct(MyStruct const&) std::remove_reference::type&& std::move(MyStruct&) 

Il semble donc que cette approche ne fonctionne pas non plus.


Cependant, si la classe cible a un membre avec un constructeur de déplacement explicite, le constructeur de déplacement généré implicitement sera visible pour la classe cible. C’est à dire avec ce code:

 #include  struct Foobar { Foobar() = default; Foobar(const Foobar&) = default; Foobar(Foobar&&) {} }; struct MyStruct { MyStruct(int x) : x(x) {} int x; Foobar f; }; int main() { MyStruct s1(4); MyStruct s2(s1); MyStruct s3(std::move(s1)); return s1.x+s2.x+s3.x; // make sure nothing is optimized away } 

MyStruct le symbole du symbole de déplacement de MyStruct, mais pas celui de la copie, car il semble être totalement implicite. Je suppose que le compilateur génère un ctor de mouvement en ligne sortingvial s’il le peut et un non-sortingvial s’il doit appeler d’autres ctors de mouvement non-sortingviaux. Cela ne m’aide toujours pas dans ma quête.

Déclarez les fonctions de membre spéciales que vous voulez exister dans MyStruct , mais ne MyStruct pas par défaut celles que vous voulez vérifier. Supposons que vous vous souciez des fonctions de déplacement et que vous souhaitiez également vous assurer que le constructeur du déplacement est noexcept :

 struct MyStruct { MyStruct() = default; MyStruct(const MyStruct&) = default; MyStruct(MyStruct&&) noexcept; // no = default; here MyStruct& operator=(const MyStruct&) = default; MyStruct& operator=(MyStruct&&); // or here }; 

Puis explicitement par défaut, en dehors de la définition de classe:

 inline MyStruct::MyStruct(MyStruct&&) noexcept = default; inline MyStruct& MyStruct::operator=(MyStruct&&) = default; 

Cela déclenche une erreur au moment de la compilation si la fonction par défaut est implicitement définie comme étant supprimée.

Comme Yakk l’a fait remarquer, ce n’est souvent pas pertinent que le compilateur soit généré ou non.

Vous pouvez vérifier si un type est sortingvial ou si aucun mouvement n’est constructible

 template< class T > struct is_sortingvially_move_constructible; template< class T > struct is_nothrow_move_constructible; 

http://en.cppreference.com/w/cpp/types/is_move_constructible

Limitation; il permet également la construction de copies sortingviales / à mémoriser.

  1. désactiver l’inline ( -fno-inline )
  2. non plus
    • assurez-vous qu’un constructeur de déménagement peut être utilisé par le code, ou (mieux)
    • append temporairement un appel à std::move(MyStruct) n’importe où dans le code compilé pour répondre à l’exigence odr-used
  3. non plus
    • assurez-vous que MyStruct a au moins une classe parent ou un membre non statique (de manière récursive), avec un constructeur de mouvement non sortingvial (par exemple, un std::ssortingng suffirait), ou (plus facilement)
    • append temporairement un membre std :: ssortingng à votre classe
  4. comstackr / lier et exécuter le fichier object résultant via nm -C ... | grep 'MyStruct.*&&' nm -C ... | grep 'MyStruct.*&&'

Le résultat impliquera si le constructeur de déplacement a été généré ou non.


Comme indiqué dans la question elle-même, cette méthode ne semblait pas fonctionner de manière fiable, mais après avoir résolu les deux problèmes qui la rendaient peu fiable: inline et sortingviality du constructeur de déménagement , elle s’est révélée être une méthode de travail.

Que le constructeur de déplacement généré soit implicitement ou explicitement défini par défaut ne joue aucun rôle – que le choix par défaut soit sortingvial ou non soit pertinent: un constructeur de déplacement (et de copie) sortingvial effectuera simplement une copie octet par pouce de l’object.