2009-12-11 6 views
2

J'ai essayé d'utiliser ce code dans VS2008 (et peut-être inclus trop contexte dans l'échantillon ...):Hériter d'un paramètre de modèle et transtypage ascendant retour en C++

class Base 
{ 
    public: 
    void Prepare() { 
     Init(); 
     CreateSelectStatement(); 
     // then open a recordset 
    } 
    void GetNext() { /* retrieve next record */ } 
    private: 
    virtual void Init() = 0; 
    virtual string CreateSelectStatement() const = 0; 
}; 
class A : public Base 
{ 
    public: 
    int foo() { return 1; } 
    private: 
    virtual void Init() { /* init logic */ } 
    virtual string CreateSelectStatement() { /* return well formed query */ } 
}; 

template<typename T> class SomeValueReader : protected T 
{ 
    public: 
    void Prepare() { T::Prepare(); } 
    void GetNext() { T::GetNext(); } 
    T& Current() { return *this; } // <<<<<<<< this is where it is interesting 
    SomeValue Value() { /* retrieve values from the join tables */ } 
    private : 
    string CreateSelectStatement() const 
    { 
    // special left join selection added to T statement 
    } 
}; 

void reader_accessAmemberfunctions_unittest(...) 
{ 
    SomeValueReader<A> reader(); 
    reader.Prepare(); 
    reader.GetNext(); 
    A a = reader.Current(); 
    int fooresult = a.foo(); 
    // reader.foo()   >> ok, not allowed 
    Assert::IsEqual<int>(1, fooresult); 
}; 

Cela fonctionne comme prévu, dire ayant accès à « a » fonctions membres et fooresult de retour 1. Toutefois, une exception est levée lorsque les objets sont supprimés à la fin de la fonction unittest:

System.AccessViolationException: tentative pour lire ou écrire protégé Mémoire. Cela est souvent une indication qu'une autre mémoire est corrompue

Si je change le type de retour de la fonction() à:

T* Current() 
{ 
    T* current = dynamic_cast<T*>(this); 
    return current; 
} 

alors tout est ok et la fin de test unitaire sans violation d'accès . Quelqu'un peut-il me dire ce qui n'allait pas avec la première implémentation Current()? Merci, bouchaet.

+0

Je suis surpris que tout code soit généré "à la fin de la fonction unittest". Il semble qu'aucun de ces objets n'a de destructeur. –

+2

Vous avez ici suffisamment de code pour le coller dans un nouveau projet, renommer la fonction unittest en 'main()', et reproduire le problème de zéro. J'essaierais ça. –

Répondre

5

Après avoir changé CreateSelectStatement pour retourner une valeur pour les fonctions mises en œuvre (pas le virtuel pur)

string CreateSelectStatement() const { return ""; } 

et modifier la déclaration du lecteur (la déclaration que vous avez doit être strictement interprété comme un prototype de fonction en C++)

SomeValueReader<A> reader; 

l'exemple ci-dessus et compile sans erreur exécute avec gcc, me conduit à penser que l'erreur réelle peut ne pas être présente dans la source ci-dessus. Malheureusement, je suis incapable de tester avec VC pour le moment.

Je ne vois pas pourquoi le changement que vous avez suggéré résoudrait le problème, la seule autre erreur que je peux voir est que Base n'a pas de destructeur virtuel déclaré ce qui signifie que si jamais vous supprimez une Base * (ou une autre classe dérivée qui n'est pas le type réel) le ou les destructeurs incorrects se déclencheront. Vous devez le déclarer comme

virtual ~Base() {} 

même s'il est vide.

D'un point de vue stylistique, il est également un peu étrange d'utiliser des modèles et des fonctions virtuelles de cette manière parce que vous utilisez ici le modèle pour résoudre les fonctions au moment de la compilation. Je ne vois pas pourquoi SomeValueReader doit dériver de T (plutôt que d'avoir une variable membre) non plus.

+0

+1 pour suggérer un destructeur virtuel. C'est aussi ce que je pensais être le problème. – strager

+0

bonne réponse ...... – KeatsPeeks

+0

+1 pour suggérer la composition au lieu de l'héritage. – avakar

1

Je n'ai pas accès à Visual Studio mais un problème avec votre code est que CreateSelectStatement() n'est pas déclaré const dans la classe A. Il a donc une signature différente des autres dans Base et SomeValueReader. C'est ok tant que vous n'essayez pas d'instancier un A (c'est-à-dire, c'est une classe virtuelle pure comme Base). Mais vous instancier un dans reader_accessAmemberfunctions_unittest. Je m'attendais à ce que votre compilateur génère une erreur pour cela .... g ++ 4.4.1 fait. Peut-être que ce n'est pas votre problème. c'est difficile à dire parce que votre exemple de code contient plusieurs autres bogues. Vous devriez essayer de garder les exemples aussi simples que possible tout en étant compilables (ils devraient contenir les fichiers d'en-tête que vous incluez par exemple). Votre code contient des instructions superflues ainsi qu'un pseudo code incompilable qui rend le travail de débogage plus difficile. Vous apprendrez beaucoup de la réduction de votre code à sa forme la plus simple et souvent vous serez en mesure de résoudre votre problème par la suite sans avoir recours à demander de l'aide. C'est une étape fondamentale dans le débogage de votre code.

+0

+1 J'ai oublié de mentionner la déclaration const manquante. –

1

Ok, mon mauvais je n'ai pas essayé de compiler le code de VS. Je le tapais simplement et omettais délibérément certains détails. Je dois dire que c'était une très mauvaise idée de ne pas tester l'échantillon directement et de ne compter que sur un comportement que je voyais dans un vrai projet. Ainsi, une version compilable serait:

/* /clr option enabled */ 

class Base 
{ 
public: 
    void FuncA() {} 
protected : 
    Base() {} 
}; 

class Derived : public Base 
{ 
public: 
    int foo() { return 1; } 
}; 

template<typename T> class SomeValueReader : protected T 
{ 
public: 
    void FuncA() { T::FuncA(); } 
    T& Current() { return *this; } 
}; 


void main(char* args) 
{ 
    SomeValueReader<Derived> reader; 
    reader.FuncA(); 
    Derived derived; 
    derived = reader.Current(); 
    int fooresult = derived.foo(); 
    //reader.foo()   >> ok, not allowed 
}; 

Maintenant, je dois dire que je ne peux pas faire cet échantillon pour produire une violation d'accès. Donc, ce n'est pas pertinent. Néanmoins, la modification que j'avais proposée était le seul mot que j'ai trouvé à mon problème dans mon vrai projet et je me demandais pourquoi.

Article 34: Composition Préfère héritage
Je suis bien au courant de cette ligne directrice générale et je ne voulais définir ma SomeValueReader en tant que composition. Cependant, SomeValueReader doit accéder aux fonctions protégées et aux membres de T pour pouvoir s'ajuster à T. Pour avoir seulement un membre T, il ne donnerait pas assez d'informations à SomeValueReader pour assumer sa responsabilité. De plus, SomeValueReader exploite l'interface non-virtuelle publique de Base en implémentant un ensemble de fonctions virtuelles privées. Sinon, il faudrait dupliquer du code ou de la logique. Donc, je me retrouve avec ces options, je soit:

  • déclarer SomeValueReader un ami de base, dupliquer certains logique et accéder aux membres protégés par T;
  • promouvoir des méthodes protégées de base à public (et exposer trop d'informations à tous);
  • ou essayez cet héritage protégé curieux (mais ajoute de la complexité).

J'ai peut-être raté une autre option. Comme je ne pouvais pas me résoudre à "tricher" avec une classe d'amis, j'ai décidé d'aller avec ce modèle de polymorphisme. Mais je suis ouvert aux suggestions.

Le manque const et destructor
Le const était une erreur due à l'inattention (et essayer de ne pas compiler le code).
Le destructeur manquant était une omission car je ne le voyais pas comme un détail important. Mais peu de gens pensent que cela aurait pu être une erreur. En fait, c'est ce que je voudrais bien non plus. Une corruption de mémoire nous conduit à chercher l'erreur à l'intérieur des destructeurs. Mais dans le vrai projet, le destructeur de base est en fait public et virtuel. Ou pourrait avoir été protégé et non-virtuel dans cet exemple puisque Base * n'a pas été utilisé.

+0

Le problème doit se situer ailleurs, je serais inquiet que le changement que vous avez fait le cache plutôt que de le réparer d'une manière ou d'une autre. Une autre chose à vérifier est que vous n'avez pas plusieurs classes/structures avec le même nom (et l'espace de noms) mais des définitions différentes. Dans notre expérience, VC mélange souvent les destructeurs pendant la liaison, ce qui entraîne des défauts de segmentation lorsque lesdites structures sont désallouées. Évidemment, c'est une erreur de le faire, mais le compilateur vous laissera le faire. –