Domanda

Abbiamo una limitazione che una classe non può fungere da classe base per più di 7 classi. C'è un modo per far rispettare la regola di cui sopra in fase di compilazione?

Sono a conoscenza della tecnica Usable_Lock di Andrew Koenig per impedire che una classe venga ereditata, ma fallirebbe solo quando proviamo a creare un'istanza della classe. Questo non può essere fatto quando si deriva da sé?

Alla classe base è permesso sapere chi sono i suoi figli. Quindi immagino che possiamo dichiarare una combinazione di amici classi e incapsularle per applicare questa regola. Supponiamo di provare qualcosa del genere

class AA {
   friend class BB;
   private:
      AA() {}
      ~AA() {}
};

class BB : public AA {

};

class CC : public AA 
{};

La derivazione della classe CC genererebbe un avviso del compilatore rispetto a un dente inaccessibile. Possiamo quindi segnalare avvisi come errori che utilizzano modifiche al compilatore (come contrassegnare tutti gli avvisi come errori), ma non vorrei fare affidamento su tali tecniche.

Un altro modo, ma per me sembra piuttosto goffo è: -

class B;

class InheritanceRule{
    class A {
    public:
        A() {}
        ~A() {}
    };
    friend class B;
};

class B {
public:
    class C : public InheritanceRule::A
    {};
};


class D : public InheritanceRule::A{};

La derivazione della classe D verrà contrassegnata come un errore del compilatore, il che significa che tutte le classi da derivare dovrebbero essere derivate all'interno della classe B. Ciò consentirà almeno un'ispezione del numero di classi derivate dalla classe A ma non impedirebbe a nessuno dall'aggiunta di altro.

Qualcuno qui che ha un modo di farlo? Meglio ancora se la classe base non ha bisogno di sapere chi sono i suoi figli.

NOTA: la classe che funge da classe base può essere istanziata (non è astratta).

Grazie in anticipo,

EDIT-1: come da commento di jon.h, una leggera modifica

// create a template class without a body, so all uses of it fail
template < typename D> 
class AllowedInheritance;

class Derived; // forward declaration
// but allow Derived by explicit specialization 
template<> 
class AllowedInheritance< Derived> {};

template<class T>
class Base : private AllowedInheritance<T> {};

// privately inherit Derived from that explicit specialization    
class Derived : public Base<Derived> {};

// Do the same with class Fail Error
// it has no explicit specialization, so it causes a compiler error
class Fail : public Base<Fail> {}; // this is error

int main()
{   
   Derived d;

   return 0;
}
È stato utile?

Soluzione

Sono stanco di schifo, riesco a malapena a tenere gli occhi aperti, quindi probabilmente c'è un modo più elegante per farlo, e certamente non sto appoggiando l'idea bizzarra che una Base dovrebbe avere al massimo sette sottoclassi.

// create a template class without a body, so all uses of it fail
template < typename D, typename B> class AllowedInheritance;


class Base {};
class Derived; // forward declaration

// but allow Derived, Base by explicit specialization 

template<> class AllowedInheritance< Derived, Base> {};

// privately inherit Derived from that explicit specialization    
class Derived : public Base, private AllowedInheritance<Derived, Base> {};


// Do the same with class Compiler Error
// it has no explicit specialization, so it causes a compiler error
class CompileError: public Base, 
     private AllowedInheritance<CompileError, Base> { };

//error: invalid use of incomplete type 
//‘struct AllowedInheritance<CompileError, Base>’


int main() {

   Base b;
   Derived d;
   return 0;
}

Commento di jon.h:

  

Come si ferma ad esempio: class Fail: public Base {}; ? \

Non lo fa. Ma poi nemmeno l'esempio originale del PO.

All'OP: la tua revisione della mia risposta è praticamente un'applicazione diretta del " di Coplien ; Modello di modello curiosamente ricorrente & Quot; ]

Lo avevo considerato anche io, ma il problema è che non esiste una relazione di ereditarietà tra derived1 : pubic base<derived1> e derived2 : pubic base<derived2>, poiché base<derived1> e base<derived2> sono due classi completamente non correlate.

Se la tua unica preoccupazione è l'ereditarietà dell'implementazione, questo non è un problema, ma se vuoi l'ereditarietà dell'interfaccia, la tua soluzione non funziona.

Penso che ci sia un modo per ottenere sia l'ereditarietà che una sintassi più pulita; come ho già detto, ero piuttosto stanco quando ho scritto la mia soluzione. Se non altro, rendendo RealBase una classe base di Base nel tuo esempio è una soluzione rapida.

Probabilmente ci sono molti modi per ripulirlo. Ma voglio sottolineare che sono d'accordo con markh44: anche se la mia soluzione è più pulita, stiamo ancora ingombrando il codice a sostegno di una regola che ha poco senso. Solo perché questo può essere fatto, non significa che dovrebbe essere.

Se la classe base in questione ha dieci anni e è troppo fragile per essere ereditata, la vera risposta è risolverla.

Altri suggerimenti

Siamo spiacenti, non so come applicare questo limite utilizzando il compilatore.

Personalmente non mi preoccuperei di forzare la regola nel codice stesso - stai ingombrando il codice con cose che non hanno nulla a che fare con ciò che il codice sta facendo - non è un codice pulito.

Invece di saltare attraverso i cerchi, proverei a rilassare quella regola. Invece dovrebbe essere una linea guida che potrebbe essere rotta se necessario e in accordo con gli altri membri del team.

Certo, mi manca la conoscenza di esattamente cosa stai facendo, quindi la regola potrebbe essere appropriata, ma in generale probabilmente non lo è.

Qualsiasi programmazione " regola " che dice che non devi mai fare x o devi sempre fare y è quasi sempre sbagliato! Nota la parola & Quot; quasi & Quot; lì dentro.

A volte potresti aver bisogno di più di 7 classi derivate - cosa fai allora? Salta attraverso più cerchi. Inoltre, perché 7? Perché non 6 o 8? È così arbitrario - un altro segno di una cattiva regola.

Se devi farlo, come dice JP, l'analisi statica è probabilmente il modo migliore.

Molti dei vari strumenti di analisi del codice statico forniscono informazioni sulla gerarchia dell'ereditarietà. Invece di provare a gestirlo nel tuo codice, esaminerei uno strumento che potrebbe impostare alcune regole per la gerarchia dell'ereditarietà e fallire la compilazione se tali regole non vengono seguite. Potrebbe costare un po 'di $ e potresti dover scrivere una regola personalizzata (ho visto la profondità dell'ereditarietà, ma non l'ereditarietà & Quot; larghezza & Quot; come desideri). Ma, a lungo termine, penso che sia la soluzione migliore.

Per commento: ho usato Coverity con un certo successo. Un po 'dispendioso. Ci sono diversi buoni thread SO che potrebbero avere opzioni migliori.

Invece di ingombrare il codice con asserzioni, potresti essere in grado di utilizzare qualcosa come GCC-XML , che analizza il sorgente C ++ utilizzando il frontend del compilatore g ++ e genera output XML. Mi aspetto che sarebbe ragionevolmente semplice sviluppare uno strumento che analizzi questo risultato e controlli le violazioni della regola; questo potrebbe quindi essere integrato con il check-in del codice sorgente.

A proposito, avendo le classi di base a conoscenza dei loro discendenti viola il Principio aperto-chiuso , il che significa che in realtà riduce l'utilità della programmazione OO in generale. Il motivo principale per separare il codice in classi base e sottoclassi è che la classe base non deve conoscere sulle sue sottoclassi - questo rende possibili cose come i pacchetti plugin consegnati dopo installazione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top