2009-11-30 15 views
3

Je souhaite ajouter une fonctionnalité d'échange à deux classes C++ existantes. Une classe hérite de l'autre. Je souhaite que les instances de chaque classe ne puissent être échangées qu'avec des instances de la même classe. Pour le rendre semi-concret, disons que j'ai des cours Foo et Bar. Bar hérite de Foo. Je définis Foo :: swap (Foo &) et Bar :: swap (Bar &). Bar :: échange les délégués vers Foo :: swap. Je veux que Foo :: swap ne fonctionne que sur les instances de Foo et que Bar :: swap ne fonctionne que sur les instances de barre: je n'arrive pas à comprendre comment appliquer cette exigence.Problème d'échange C++ dans le scénario d'héritage

Voici un échantillon de ce qui me donne du mal:

#include <algorithm> 
#include <iostream> 

struct Foo { 
    int x; 
    Foo(int x) : x(x) {}; 

    virtual void swap(Foo &other) { 
     std::cout << __PRETTY_FUNCTION__ << std::endl; 
     std::swap(this->x, other.x); 
    }; 
}; 

struct Bar : public Foo { 
    int y; 
    Bar(int x, int y) : Foo(x), y(y) {}; 

    virtual void swap(Bar &other) { 
     std::cout << __PRETTY_FUNCTION__ << " "; 
     Foo::swap(other); 
     std::swap(this->y, other.y); 
    }; 
}; 

void display(Foo &f1, Foo &f2, Bar &b34, Bar &b56) 
{ 
    using namespace std; 

    cout << "f1: " << f1.x     << endl; 
    cout << "f2: " << f2.x     << endl; 
    cout << "b34: " << b34.x << " " << b34.y << endl; 
    cout << "b56: " << b56.x << " " << b56.y << endl; 
} 

int main(int argc, char **argv) 
{ 
    { 
     Foo f1(1), f2(2); 
     Bar b34(3,4), b56(5,6); 
     std::cout << std::endl << "Initial values: " << std::endl; 
     display(f1,f2,b34,b56); 
    } 

    { 
     Foo f1(1), f2(2); 
     Bar b34(3,4), b56(5,6); 
     std::cout << std::endl << "After Homogeneous Swap: " << std::endl; 
     f1.swap(f2);    // Desired 
     b34.swap(b56);   // Desired 
     display(f1,f2,b34,b56); 
    } 

    { 
     Foo f1(1), f2(2); 
     Bar b34(3,4), b56(5,6); 
     std::cout << std::endl << "After Heterogeneous Member Swap: " << std::endl; 
     // b56.swap(f2);   // Doesn't compile, excellent 
     f1.swap(b34);   // Want this to not compile, but unsure how 
     display(f1,f2,b34,b56); 
    } 

    return 0; 
} 

Voici le résultat:

Initial values: 
f1: 1 
f2: 2 
b34: 3 4 
b56: 5 6 

After Homogeneous Swap: 
virtual void Foo::swap(Foo&) 
virtual void Bar::swap(Bar&) virtual void Foo::swap(Foo&) 
f1: 2 
f2: 1 
b34: 5 6 
b56: 3 4 

After Heterogeneous Member Swap: 
virtual void Foo::swap(Foo&) 
f1: 3 
f2: 2 
b34: 1 4 
b56: 5 6 

Vous pouvez le voir dans le groupe de sortie finale où f1.swap (B34) "découpé en tranches" b34 d'une manière potentiellement méchante. Je voudrais que la ligne coupable ne soit pas compilée ou explosée à l'exécution. En raison de l'héritage impliqué, je pense que je rencontre le même problème si j'utilise une implémentation de non-membre ou d'échange d'ami.

Le code est disponible à codepad si cela aide. Ce cas d'utilisation est dû à I want to add swap to boost::multi_array and boost::multi_array_ref. multi_array hérite de multi_array_ref. Cela n'a de sens que d'échanger multi_arrays avec multi_arrays et multi_array_refs avec multi_array_refs.

Répondre

3

(peu solution hacky)

Ajouter une méthode virtuelle protégée, isBaseFoo(), faire revenir vrai dans Foo, et faux dans la barre, plus la méthode d'échange pour Foo pourrait vérifier est l'argument a isBaseFoo() == vrai.

Evil, et ne détecte le problème qu'à l'exécution, mais je ne peux pas penser à quelque chose de mieux, bien que la réponse de Charles Bailey pourrait être meilleure, si vous autorisez dynamic_cast <>.

+0

Vraiment moche, mais je pense que c'est la seule chose qui est réalisable en pratique. Merci Douglas. –

2

Vous ne pouvez pas vraiment faire cela, mais je ne vois pas le point, de toute façon. Ce n'est pas pire que de trancher sur operator= ou de copier des constructeurs, et vous ne pouvez pas non plus éviter ce dernier. Pourquoi swap serait-il différent?

Pour la même raison, cela ne vaut probablement pas la peine de rendre virtuel swap, pour la même raison que vous ne créez pas operator= virtuel.

+1

Il ne m'est pas venu à l'esprit que c'est un problème de tranchage standard présent dans beaucoup d'autres endroits. Je suis beaucoup moins inquiet à ce sujet maintenant que les gens ont déjà des problèmes avec cette catégorie de problèmes. –

5

L'échange, l'affectation et la comparaison fonctionnent bien avec les types de valeur et ne fonctionnent pas correctement avec les bases des hiérarchies de classes.

J'ai toujours trouvé qu'il était plus facile de suivre la recommandation de Scott Meyer sur le C++ effectif de ne pas dériver de classes concrètes et de ne faire que des classes de feuilles concrètes. Vous pouvez ensuite implémenter en toute sécurité swap, operator ==, etc. comme des fonctions non virtuelles pour les nœuds feuilles uniquement. Bien qu'il soit possible d'avoir des fonctions de permutation virtuelle, le fait d'avoir des classes de base virtuelles est d'avoir un comportement dynamique à l'exécution, donc je pense que vous êtes sur un perdant essayant d'échouer toutes les possibilités incorrectes à la compilation.

Si vous souhaitez utiliser la route d'échange virtuel, une approche possible consiste à effectuer une opération similaire.

class Base 
{ 
public: 
    virtual void Swap(Base& other) = 0; 
}; 

class ConcreteDerived 
{ 
    virtual void Swap(Base& other) 
    { 
     // might throw bad_cast, in this case desirable 
     ConcreteDerived& cother = dynamic_cast<ConcreteDerived&>(other);bad_cast 
     PrivateSwap(cother); 
    } 

    void PrivateSwap(ConcreteDerived& other) 
    { 
     // swap implementation 
    } 
}; 
+0

Merci pour la suggestion, Charles. J'ai besoin de Base :: Swap pour être non-virtuel à cause de mon cas d'utilisation. Je ne suis pas sûr de pouvoir l'adopter puisque la distribution dynamique utilisée dans un fichier Base :: Swap similaire à ConcreteDerived :: Swap réussira toujours. –

+0

Si votre classe de base Swap n'est pas virtuelle, vous aurez peu ou pas de protection contre le découpage. Toute classe que vous ajoutez à la hiérarchie aura une action d'échange par défaut qui sera découpée. Sans un comportement polymorphique, vous ne pourrez pas échanger deux classes dérivées par des pointeurs ou une référence à leur classe de base. Une fonction virtuelle serait la manière la plus naturelle de le faire. –

0

Ce que vous essayez en fait de faire est d'échanger des instances de classes d'une hiérarchie d'héritage tierce. Compte tenu de cela, j'oublierais d'utiliser swap sur les classes réelles et ajouter un niveau d'indirection. Utiliser boost :: shared_ptr est une bonne approche; utilisez des instances shared_ptr contenant la classe que vous voulez et permutez le contenu de votre coeur.

En général, résoudre le problème tel que vous l'avez demandé au moment de la compilation est difficile pour toutes les raisons décrites par les autres répondeurs.

1

Je pense que ce scénario est maintenant adressé par la présence de sémantique de déplacement dans C++ 11. J'utilise généralement la fonction swap uniquement pour éviter la duplication de copie dans les affectations, donc elle est utilisée statiquement, par la classe dérivée qui a besoin d'étendre la fonction swap et qui connaît le type statique de sa base. comme il a été indiqué peut conduire à des problèmes subtils avec le découpage). En fait, je déclare la fonction swap comme une méthode protégée dans ma base pour m'assurer qu'elle n'est utilisée directement nulle part ailleurs. La classe la plus concrète peut alors avoir la version finale publique si nécessaire.