2009-07-17 12 views
2

Mise à jour: exemple de code modifié pour utiliser AutoA pour la solution de contournement (qui était l'intention d'origine). Réalisé ceci après avoir vu la réponse de rlbond.std :: auto_ptr Problème de compilation dans Visual Studio 6.0

Je suis en train d'intégrer l'utilisation de auto_ptr dans mon code en fonction des recommandations de ce fil:

Express the usage of C++ arguments through method interfaces

Cependant, je reçois une inattendue erreurs de compilation lors de la compilation avec Visual Studio 6.0. Il a un problème en traitant des affectations/copies d'un std::auto_ptr d'un type dérivé à un std::auto_ptr du type de base. Est-ce un problème spécifique à mon compilateur?

Je sais qu'il y a une forte recommandation d'utiliser Boost, mais sur mon projet ce n'est pas une option. Si je veux toujours utiliser auto_ptr, suis-je obligé d'utiliser la solution de contournement d'appeler std::auto_ptr::release()? De ce que j'ai rencontré jusqu'à présent, ce problème entraîne une erreur de compilation, il est donc assez facile à attraper. Cependant, l'adoption de la convention d'appel de release pour assigner un 'auto_ptr' du type de base pourrait-elle m'exposer à des problèmes de maintenance? Surtout si construit avec un compilateur différent (en supposant que les autres compilateurs n'ont pas ce problème).

Si la solution de contournement release() n'est pas bonne en raison de ma situation, devrais-je me référer à une autre convention pour décrire le transfert de propriété?

Voici un exemple illustrant le problème.

#include "stdafx.h" 
#include <memory> 

struct A 
{ 
    int x; 
}; 

struct B : public A 
{ 
    int y; 
}; 

typedef std::auto_ptr<A> AutoA; 
typedef std::auto_ptr<B> AutoB; 

void sink(AutoA a) 
{ 
    //Some Code.... 
} 

int main(int argc, char* argv[]) 
{ 
    //Raws to auto ptr 
    AutoA a_raw_to_a_auto(new A()); 
    AutoB b_raw_to_b_auto(new B()); 
    AutoA b_raw_to_a_auto(new B()); 

    //autos to same type autos 
    AutoA a_auto_to_a_auto(a_raw_to_a_auto); 
    AutoB b_auto_to_b_auto(b_raw_to_b_auto); 

    //raw derive to auto base 
    AutoB b_auto(new B()); 

    //auto derive to auto base 
    AutoA b_auto_to_a_auto(b_auto); //fails to compile 

    //workaround to avoid compile error. 
    AutoB b_workaround(new B()); 
    AutoA b_auto_to_a_auto_workaround(b_workaround.release()); 

    sink(a_raw_to_a_auto); 
    sink(b_raw_to_b_auto); //fails to compile 

    return 0; 
} 

Compile Erreur:

Compiling... 
Sandbox.cpp 
C:\Program Files\Microsoft Visual Studio\MyProjects\Sandbox\Sandbox.cpp(40) : error C2664: '__thiscall std::auto_ptr<struct A>::std::auto_ptr<struct A>(struct A *)' : cannot convert parameter 1 from 'class std::auto_ptr<struct B>' to 'struct A *' 
     No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called 
C:\Program Files\Microsoft Visual Studio\MyProjects\Sandbox\Sandbox.cpp(47) : error C2664: 'sink' : cannot convert parameter 1 from 'class std::auto_ptr<struct B>' to 'class std::auto_ptr<struct A>' 
     No constructor could take the source type, or constructor overload resolution was ambiguous 
Error executing cl.exe. 

Sandbox.exe - 2 error(s), 0 warning(s) 
+4

Sérieusement? un compilateur de 11 ans? On pourrait penser que les gens iraient de l'avant. – shoosh

+0

Vous penseriez qu'il y aurait quelque chose à déplacer. 10 est le nouveau 6, mais 10 est toujours en version bêta. –

Répondre

5

Le premier est facile:

AutoA b_auto_to_a_auto(b_auto); //fails to compile 

Cela échoue sur VC6 car il nécessite modèles de fonction de membre, la bibliothèque standard de quelque chose VC6 ne supporte pas. Il compile sur les compilateurs compatibles standard, cependant.

Solution:

AutoA b_auto_to_a_auto(b_auto.release()); 

Le second est beaucoup plus subtile :)

sink(b_raw_to_b_auto); //fails to compile 

Celui-ci ne doit pas compiler un compilateur conforme aux normes, parce qu'il ya une conversion implicite en cours. Le compilateur transforme le dessus dans

sink(std::auto_ptr<A>(b_raw_to_b_auto)); 

cependant sink prend std::auto_ptr<A>parvaleur, de sorte que le std::auto_ptr<A> temporaire créé implicitement par le compilateur doit être copie construite dans l'argument sink. Maintenant, les temporaires comme ça sont rvalues ​​. Les Rvalues ​​ne se lient pas aux références non-const, mais le "constructeur de copie" de std::auto_ptr prend son argument par référence non-const.

Voilà, compilez l'erreur. AFAICS c'est un comportement conforme aux normes. C++ - 0x "déplacer sémantique" va corriger cela en ajoutant un "constructeur de copie" qui prend une référence rvalue, bien que je ne sache pas combien d'amour std::auto_ptr recevra encore à l'avenir, quoi avec std::shared_ptr et tous.

Solution pour le second:

AutoA tmp(b_raw_to_b_auto/*.release() for VC6*/); 
sink(tmp); 
+0

Merci pour la réponse. J'avais le sentiment que ça avait à voir avec les templates de VC6, j'ai déjà eu des problèmes avec ça. Je m'attendais à ce que tout était un problème avec VC6, intéressant de découvrir les raisons pour lesquelles non. Je ne connaissais pas ce détail des valeurs, c'est une bonne information à connaître. Après être rentré à la maison, j'ai pu obtenir les mêmes résultats de construction que ce que vous dites est conforme à la norme. Merci à tous pour vos réponses. –

4
AutoA b_auto_to_a_auto(b_auto); //fails to compile 

sink(b_raw_to_b_auto); //fails to compile 

Pavel Minaev signale quelque chose que je ne savais pas en fait:

Le premier appel doit compiler, parce qu'il ya une conversion implicite d'un B * à un A *. Cependant, la seconde ne compilera pas. Ce qui suit sera:

sink(static_cast<AutoA>(b_raw_to_b_auto)); 

VC6 est notoire pour ne pas être très bon avec les modèles.

Je vous suggère fortement de mettre à niveau votre base de code vers celle qui fonctionne réellement et commencer à utiliser les techniques RAII, en particulier boost::shared_ptr. Je sais que vous dites que vous ne pouvez pas, et je sais que c'est difficile, mais vous aurez pratiquement aucune fuite de mémoire et beaucoup, beaucoup moins de bugs.

Puis encore, peut-être même sans fonctionnalité complète, vous pouvez utiliser auto_ptr?

+0

Ma compréhension de auto_ptr était qu'il était prévu d'avoir une sémantique similaire à celle d'un pointeur régulier.Par exemple, si j'avais une fonction de évier vide (A * a); Je pourrais faire un appel comme ceci: B * b = new B(); évier (b); Si c'est une limitation qui ne peut pas être évitée en utilisant std :: auto_ptr, alors je devrai reconsidérer son utilité pour ce que j'essaie de faire ou mon approche. Votre commentaire contre sink est en fait le comportement désiré dans mon cas, car j'essaie de transférer la propriété de l'objet pointé par le passé dans auto_ptr. –

+0

Tout ce que je veux dire, c'est qu'un auto_ptr est capable de posséder un objet, et que l'assignation ou la copie transfère la propriété. Beaucoup de gens croient que auto_ptr est un mauvais design à cause de cela. Par exemple, auto_ptr ne peut pas être utilisé dans les conteneurs STL. Tant que vous savez ce que vous faites, c'est bon, mais l'affectation ne fonctionne pas comme un pointeur régulier. Je sais que vous avez dit que boost n'est pas une option, mais shared_ptr de boost est vraiment ce que vous voulez si vous avez besoin d'une propriété partagée. – rlbond

+0

"Ce sont des types non apparentés, en dépit de la relation entre A et B". et "Si vous voulez traiter A et B de manière polymorphique, utilisez simplement un auto_ptr ." Basé sur cela, je vais supposer que boost :: shared_ptr aurait le même problème? –

2

Il y a deux problèmes ici. Tout d'abord, ceci:

AutoA b_auto_to_a_auto(b_auto); 

Il est parfaitement conforme à la norme et devrait compiler. Laissez-moi vous expliquer pourquoi. La norme ISO C++ spécifie (20.4.5.1 [lib.auto.ptr.cons]/4-6) le constructeur suivant pour auto_ptr<X> (entre autres);

template<class Y> auto_ptr(auto_ptr<Y>&) throw(); 

Notez que Y est un type différent de X ici. Et la norme dit en outre:

Requires: Y* can be implicitly converted to X*.

La seule chose à faire attention ici est que l'argument du constructeur est une référence à la non-const. Pour votre cas, ce n'est pas un problème (puisque vous y passez une variable non-const), mais cela devient important pour la partie suivante. Pour conclure ici: ce que vous voyez est comportement non-standard dans VC6. Il devrait compiler sur un compilateur conforme (et compilera sur VC7 et au-dessus). Passons maintenant à la deuxième chose:

sink(b_raw_to_b_auto); //fails to compile 

Celui-ci est en fait parfaitement expliqué par mmutz, donc je ne vais pas entrer dans les détails - voir sa réponse. Pour conclure sur ce point: oui, cette ligne ne devrait pas compiler, et ne le sera pas dans un compilateur conforme (ou VC6, comme vous l'avez découvert).

Questions connexes