2010-08-20 9 views
4

J'étais sûr que la réponse à cette question était, "Jamais, jamais un modèle ne peut être le constructeur de copie." Malheureusement, je viens de passer 3 heures à comprendre pourquoi j'obtenais un avertissement sur la récursivité, je l'ai dépisté sur le constructeur de la copie, j'ai regardé le débogueur devenir fou et ne pas me laisser regarder le code récursif un '&' manquant dans un constructeur de base.MI et bug de constructeur de copie implicite (était: Sous quelles conditions un template peut-il être le constructeur de copie?)

Vous voyez, j'ai cet hôte de conception complexe basé sur des règles qui fonctionne correctement depuis un certain temps maintenant. Je suis allé sur la redéfinition de deux politiques en une et couru dans un constructeur de copie récursive. Réduit à une politique qui est nécessaire pour fournir un constructeur qui peut prendre un type de concept XXX comme argument, mais dans ce cas, je suis juste en train de le rejeter. Alors, je l'ai écrit

struct my_policy 
{ 
    template < typename T > 
    my_polity(T const) {} // missing '&'...oops 
}; 

Maintenant, my_policy est une classe de base à l'hôte (bien sûr) et cette petite faute de frappe causé récursion où le constructeur de copie de l'hôte lui-même passé la chaîne à cela, le constructeur basé sur un modèle plutôt qu'un implicite, constructeur de copie généré par le compilateur. Il faudrait alors rappeler son constructeur de copie pour créer le temporaire. La chose vraiment fascinante est que je ne peux pas recréer ceci en code simplifié. Même avec une sorte d'exemple d'hôte de politique factice, je ne peux pas y arriver. Le code suivant ne présente pas la question:

#include <boost/utility/enable_if.hpp> 
#include <boost/mpl/bool.hpp> 

struct base 
{ 
    template < typename T > 
    base(T const) {} 
}; 

struct another_base 
{ 
    int x; 

    another_base(int y) : x(y) {} 
}; 

template < typename T > 
struct is_derived : boost::mpl::false_ {}; 

template < typename T1, typename T2 > 
struct derived : T1, T2 
{ 
    template < typename T > 
    derived(T const& x, typename boost::disable_if< is_derived<T> >::type * = 0) : T1(0), T2(x) {} 
}; 

template < typename T1, typename T2 > 
struct is_derived<derived<T1,T2>> : boost::mpl::true_ {}; 

int main() 
{ 
    derived<base, another_base> d(23); 
    derived<base, another_base> x = d; 
} 

J'utilise la bibliothèque de paramètres de boost pour faire 7 ou si des arguments à l'hôte accessible par « nom ». Peut-être que c'est le problème, je ne sais pas. Quoi qu'il en soit, je me demande si quelqu'un sait quelles conditions spécifiques, le cas échéant, pourraient amener un compilateur à utiliser légitimement le constructeur basé sur un modèle pour "base" en tant que constructeur de copie ou du constructeur de copie implicite pour "derived".

Modifier la note:

Je recréée le problème dans le code ci-dessus en donnant « another_base » un constructeur de copie explicite:

struct another_base 
{ 
    int x; 

    another_base(another_base const& b) : x(b.x) {} 

    another_base(int y) : x(y) {} 
}; 

À partir de conclure que c'est un bug du compilateur à moins que quelqu'un peut Dites-moi pourquoi c'est légitime.

Plus d'informations:

struct derived; 

struct base 
{ 
    base() {} 

private: 
    base(derived const&); 
}; 

struct base2 
{ 
    base2() {} 
    //base2 (base2 const&) {} 
}; 

struct derived : base, base2 {}; 

int main() 
{ 
    derived d1; derived d2(d1); 
} 

En regardant de plus à la réponse de Schaub je pris le code ci-dessus et compilé. Il compile juste bien jusqu'à ce que vous décommentez la déclaration du constructeur de copie de base2. Ensuite, il va exploser de la façon dont je suppose qu'il était attendu avec le code d'origine (pas d'accès au constructeur privé dans la base). Les modèles ne font même pas partie du problème. vous pouvez recréer le problème sans eux. On dirait que c'est un problème de MI, que VS a toujours été un peu lent à faire les choses correctement.

J'ai changé les étiquettes pour refléter cette découverte.

Publié au dépôt de bug de MS

http://connect.microsoft.com/VisualStudio/feedback/details/587787/implicit-copy-constructor-calls-base-with-derived-type-under-specific-conditions

J'ai inclus un travail autour dans le code exemple.

+0

Je suis assez sûr que * template * ne peut pas être un constructeur de copie. Mais * template class * peut avoir un constructeur de copie défini. En ce qui concerne votre exemple, un code comme celui-ci devait déclencher une récursion infinie dans le constructeur de copie, mais je suppose que dans les cas simplifiés, le compilateur a optimisé les choses. – SigTerm

+0

Votre exemple est-il le bon? Je veux dire qu'il semble que 'T1' est' base' et 'T2' est' another_base' mais vous passez l'entier à 'T1' (qui a le constructeur du template) et' x' à 'T2' (qui a le' int' constructeur). – Troubadour

+0

Quel compilateur utilisez-vous? – SCFrench

Répondre

3

Je suis sûr que la réponse à votre question est "jamais".

Section 12.8.2 de la norme ANSI C++ dit

Un constructeur non-modèle pour la classe X est un constructeur copie si son premier paramètre est de type X &, const X &, volatile X & ou const volatile X &, et soit il n'y a pas d'autres paramètres ou bien tous les autres paramètres ont arguments par défaut.

Section 12.8.3 dit

Une déclaration d'un constructeur pour une classe X est mal formé si le premier paramètre est de type (éventuellement cv-qualifié) X et y soit aucun autre paramètre ou tous les autres paramètres ont des arguments par défaut. Un modèle de fonction membre n'est jamais instancié pour effectuer la copie d'un objet de classe sur un objet de son type classe . [Exemple:

struct S { 
    template <typename T> S(T); 
}; 

S f(); 

void g() { 
    S a(f()); // does not instantiate member template 
} 

- exemple final]

3

Je souhaite signaler que la déclaration suivante est NOT constructeur de copie.

template < typename T > 
    derived(T const& x, typename boost::disable_if< is_derived<T> >::type * = 0) : T1(0), T2(x) {} 

un constructeur de copie pour derived<base, another_base> devrait prendre const derived<base, another_base>& en entrée, non arbitraire const derived<X,Y>&


un constructeur de copie a comme premier paramètre une référence (éventuellement const ou volatile) à son propre type de classe . Il peut avoir plus d'arguments, mais le reste doit avoir les valeurs par défaut associées.

http://en.wikipedia.org/wiki/Copy_constructor

+0

Correct. Ce constructeur est censé être construit à partir d'un type X qui ne peut pas être une autre instanciation dérivée. Le constructeur de copie pour derived est implicitement défini par le compilateur. –

5

Avec Visual C++, il y a un problème avec dérivé à la délégation de base de l'argument du constructeur de copie:

struct Derived; 

struct Base { 
    Base(){ } 

private: 
    Base(Derived const&); 
}; 

struct Derived : Base { }; 

Derived d1; 
Derived d2(d1); 

Ce code est valide, mais Visual C++ ne parvient pas à le compiler, car ils appellent le constructeur de copie de classe de base à l'aide d'un objet Derived ct. La norme exige toutefois que le compilateur transmette un Base const (ou Base dans certains cas) jusqu'à la classe de base.

Ceci est la première partie de votre puzzle: Le modèle est une meilleure correspondance, car le constructeur de copie aurait besoin d'une conversion dérivée à base, mais votre modèle accepte directement la classe dérivée, et aurait besoin d'une autre copie. et ainsi de suite. Notez que le modèle et non agira comme un constructeur de copie ici (étant donné le bogue VC++), tout comme la déclaration ci-dessus de Base(Derived const&) n'a pas déclaré de constructeur de copie.

La deuxième partie est la réponse à votre autre question: La norme était ambiguë et pas claire dans C++ 03 quant à savoir si les modèles instanciés pourraient agir en tant que constructeurs de copie ou non. Dans une note, il est dit

Parce qu'un constructeur de modèle n'est jamais un constructeur de copie, la présence d'un tel modèle ne supprime pas la déclaration implicite d'un constructeur de copie. Les constructeurs de modèles participent à la résolution de surcharge avec d'autres constructeurs, y compris les constructeurs de copie, et un constructeur de modèle peut être utilisé pour copier un objet s'il fournit une meilleure correspondance que les autres constructeurs.

mais quelques paragraphes ci-dessous qui, il dit

Un modèle de fonction membre est jamais instancié pour effectuer la copie d'un objet de classe à un objet de ce type de classe.

En raison du contexte que ce texte apparaît sur (interdiction par valeur des constructeurs de copie des paramètres), on peut faire valoir que cela ne forbiding une instanciation d'un par référence constructeur de copie d'un modèle. Mais de telles discussions sont discutables, face à cette formulation vague.

Le FCD C++ 0x l'a clearifié et a supprimé la note étrange. Il est maintenant clair que les modèles ne sont jamais instanciés pour effectuer une copie, peu importe si elle céderait aux paramètres par référence ou par valeur. Mais comme expliqué ci-dessus, si vous utilisez VC++ par hasard, et qu'il présente ce comportement, cela n'a rien à voir avec les constructeurs de copie.

+0

Tout cela est probablement bien au-dessus de ma tête mais aurais-je raison de dire que si les templates ne sont pas utilisés comme constructeurs de copie, alors la structure 'derived' dans la question n'utilise qu'un constructeur de copie synthétisé? – Troubadour

+0

@Troubadour oui si les instanciations de gabarit ne sont pas utilisées pour les constructeurs de copie, alors il utilisera toujours le constructeur de copie synthétisé dans la base. Il y a beaucoup à ce sujet ... J'ai récemment eu un échange de courrier avec quelqu'un d'autre et beaucoup de sujets ont été soulevés à ce sujet .. c'est désordonné :) –

+0

@litb: D'accord, mais le point que je veux dire c'est que dans l'exemple simplifié 'derived' _itself_ devrait utiliser un constructeur de copie synthétisé? Si oui, alors l'exemple fonctionne trivialement et l'étrangeté est alors comment @Noah l'a fait pour casser en définissant un constructeur de copie pour 'another_base'. Il est bien au-delà de mon heure de coucher, donc je suis peut-être incohérent ... – Troubadour

Questions connexes