2017-09-20 3 views
0

J'ai un problème que j'essaie de résoudre où j'aimerais avoir une classe de base abstraite, qui hérite de plusieurs classes, où les dérivations peuvent éventuellement avoir leurs méthodes substitué sans déclarer une classe supplémentaire. Je vais vous donner un exemple ici de ce que je suis en train de réaliser:Besoin de recommandations sur l'héritage multiple et le remplacement facultatif de fonction C++

#include <iostream> 

using namespace std; 

class A 
{ 
    public: 
     virtual void foo() = 0; 

    protected: 
     A() {} 
}; 

class A1 : public A 
{ 
    public: 
     A1() : A() {} 

     void foo() { cout << "A1 foo" << endl; }; 
}; 

class A2 : public A 
{ 
    public: 
     A2() : A() {} 

     void foo() { cout << "A2 foo" << endl; }; 
}; 

class B 
{ 
    public: 
     virtual void bar() { cout << "B bar: " << endl; } 
}; 

class B1 : public B 
{ 
    public: 
     void bar() 
     { 
      cout << "B1 bar wrapper begin" << endl; 
      B::bar(); 
      cout << "B1 bar wrapper end" << endl; 
     } 
}; 

/* 
    ??? 
    pure virtual class C 
    enforce derived classes to inherit something of type A 
    enforce derived classes to inherit something of type B 

    class C1 : public A1, either B or B1 ??? templates??? 
    { 

    } 

    class C2 : public A2, either B or B1 ??? templates??? 
    { 

    } 

    Can this be done without having to define classes CA1B, CA2B, CA1B1, CA2B1, etc.? 
*/ 

int main(int argc, char *argv[]) 
{ 
    A1 a1; 
    a1.foo(); 
    A2 a2; 
    a2.foo(); 

/* 
    C1 c1b with type B 
    C1 c1b1 with type B1 
    C2 c2b with type B 
    C2 c2b1 with type B1 

    put c1b, c1b1, c2b, c2b1 in a list named "combinations" 

    cout << "Printing combinations" << endl; 
    for (auto i : combinations) 
    { 
     i->foo(); 
     i->bar(); 
    } 
*/ 

    return 0; 
} 

En théorie, la sortie serait:

A1 foo 
A2 foo 
Printing combinations 
A1 foo 
B bar 
A1 foo 
B1 bar wrapper begin 
B bar 
B1 bar wrapper end 
A2 foo 
B bar 
A2 foo 
B1 bar wrapper begin 
B bar 
B1 bar wrapper end 

S'il y a un moyen d'y arriver par un certain modèle de conception, ou J'utilise une mauvaise approche, s'il vous plaît faites le moi savoir. J'utilise C++ 11.

Répondre

3

Votre cas d'utilisation crie "modèles avec contraintes". Ce qui vous manque, c'est comment vérifier et encoder que les paramètres du template héritent des classes correctes. Vous pouvez le faire avec std::is_base_of

template<class A_, class B_, 
     typename std::enable_if<std::is_base_of<A, A_>::value>, int>::type = 0, 
     typename std::enable_if<std::is_base_of<B, B_>::value>, int>::type = 0> 
class C : public A_, public B_ 
{ 

}; 

Voici comment cela fonctionne:

std::enable_if aura un type (un int comme dans notre cas), ssi l'expression booléenne il est alimenté est vrai. Sinon, il n'y a pas de type et le modèle ne sera pas compilé. S'il y a un type ici, alors nous avons obtenu un paramètre de template non-type, auquel nous donnons la valeur par défaut 0. L'affectation de la valeur par défaut est ce qui nous permet d'instancier le modèle avec deux arguments.

Vous trouverez ces utilitaires, et plus, dans l'en-tête <type_traits>.

+0

Est-ce que quelque chose comme ça peut être utilisé de façon polymorphe? Par exemple, si je crée un objet 'shared_ptr >' et un autre objet 'shared_ptr >', ces deux éléments peuvent-ils être stockés dans une 'liste >>'? Je ne suis pas sûr de ce qui est requis dans la classe C pour convertir une version dérivée à 'C '. – George

+0

@George - Non. Soit avec un modèle ou explicitement épelé (comme dans votre question) 'C ' et 'C ' ne sont pas dans une relation polymorphe. – StoryTeller

+0

@George - Mais vous pouvez mettre toutes ces classes dans un conteneur de 'B *' ou un conteneur de 'A *'. – StoryTeller

0

Je me suis intéressé à ce sujet il y a quelque temps, donc j'ai quelques stratégies. Je pense qu'un programme de socket est la démonstration parfaite de ce type d'abstraction. L'une des choses les plus simples à faire est de déclarer un en-tête de base avec les fonctions et les variables que vous voulez avoir dans n'importe quel objet de la classe que vous déclarez. Créez ensuite des classes qui héritent de la classe de base lorsque ITSELF est défini en tant qu'argument de modèle. Par exemple:

template<class P> class Socket { 
    protected: 
    int sock; 
    char datagram[4096]; 
    struct sockaddr_in server; 
    struct sockaddr_in b; 
    public: 
    void dport(int port){server.sin_port=htons(port);} 
    int CONNECT(){return connect(sock , (struct sockaddr *)&server , sizeof(server));}; 
    template <class Dst> inline void dst(Dst d){server.sin_addr.s_addr=inet_addr(d);} 
    template <class Src> void src(Src s){b.sin_addr.s_addr=inet_addr(s);} 
    int LISTEN(int port){b.sin_port=htons(port); return bind(sock, 
    (sockaddr*)&b, sizeof(b));} 

Il existe plusieurs façons de vous spécialiser à ce stade. Mon préféré serait de créer de nouvelles classes qui hériteraient en passant leur propre nom comme argument de base.

class UDP : public Socket<UDP> { 
public: 
    UDP(){server.sin_family=AF_INET; 
    this->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);} 
    template <class Msg> int SEND(Msg m, int length){ 
    return sendto(this->sock, m, length, MSG_DONTWAIT, (sockaddr*)&this- 
    >server, sizeof(this->server));} 
    void RECV(void* buf, size_t size){socklen_t fromlen = sizeof(sockaddr); 
    ssize_t i = recvfrom(this->sock, buf, size, MSG_WAITALL, 
    (sockaddr*)&this->b, &fromlen);} 
}; 

class TCP : public Socket<TCP> { 
public: 
TCP(){this->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); this- 
>server.sin_family=AF_INET;} 
int SEND(std::string buf){return send(this->sock, buf.c_str(), buf.size(), 0);} //TODO: Error Handling 
void RECV(){char cur; while (read(this->sock, &cur, 1) > 0) {cout << cur;}}}; 

Maintenant les sockets UDP utilisent recvfrom() et sendto() alors que les sockets TCP utilisent send() et lire(). Tous ces appels sont maintenant valides:

Socket<TCP> t1; 
Socket<UDP> u1; 
TCP t2; 
UDP u2; 

Je ne pouvais pas comprendre aucune façon d'écrire des spécialisations de la classe de base sans duplication de code avec chacun. Il est plus facile de créer des classes dérivées.