Voorkom klasseovererving in C++

Onlangs vroeg een van mijn vrienden me hoe ik klasse-overerving in C++ kon voorkomen. Hij wilde dat de compilatie zou mislukken.

Ik zat erover na te denken en vond 3 antwoorden. Ik weet niet zeker welke de beste is.

1) Privé-constructeur(s)

class CBase
{
public:
 static CBase* CreateInstance() 
 { 
  CBase* b1 = new CBase();
  return b1;
 }
private:
 CBase() { }
 CBase(CBase3) { }
 CBase& operator=(CBase&) { }
};

2) Met behulp van CSealedbasisklasse, private ctor & virtuele overerving

class CSealed
{
private:
 CSealed() {
 }
 friend class CBase;
};
class CBase : virtual CSealed
{
public:
 CBase() {
 }
};

3) Met behulp van een CSealedbasisklasse, beschermde ctor & virtuele overerving

class CSealed
{
protected:
 CSealed() {
 }
};
class CBase : virtual CSealed
{
public:
 CBase() {
 }
};

Alle bovenstaande methoden zorgen ervoor dat de klasse CBaseniet verder kan worden geërfd.
Mijn vraag is:

  1. Wat is de beste methode? Zijn er nog andere methoden beschikbaar?

  2. Methode 2 & 3 zal niet werken tenzij de klasse CSealedvirtueel wordt geërfd. Waarom is dat ? Heeft het iets te maken met vdisp ptr ??

PS:

Het bovenstaande programma is gecompileerd in MS C++ compiler (Visual Studio).
referentie: http://www.codeguru.com/forum/ archive/index.php/t-321146.html


Antwoord 1, autoriteit 100%

Vanaf C++11 kunt u het laatste trefwoord aan uw klas toevoegen, bijvoorbeeld

class CBase final
{
...

De belangrijkste reden die ik kan zien om dit te willen doen (en de reden dat ik op zoek ben naar deze vraag) is om een ​​klasse te markeren als niet-subclasseerbaar, zodat je veilig een niet-virtuele destructor kunt gebruiken en een vtable helemaal kunt vermijden.


Antwoord 2, autoriteit 15%

Je kunt overerving niet voorkomen (vóór C++11’s finalsleutelwoord) – je kunt alleen instantiatie van overgeërfde klassen voorkomen. Met andere woorden, er is geen manier om het volgende te voorkomen:

class A { ... };
class B : public A { ... };

U kunt het beste voorkomen dat objecten van het type B worden geïnstantieerd. Als dat het geval is, raad ik je aan om het advies van kts op te volgen en het feit te documenteren dat A (of wat dan ook) niet bedoeld is om te worden gebruikt voor overerving, het een niet-virtuele destructor te geven en geen andere virtuele functies, en het daarbij te laten.


Antwoord 3, autoriteit 9%

Je gaat door verdraaiingen om verdere subclassificatie te voorkomen. Waarom? Documenteer het feit dat de klasse niet uitbreidbaar is en maak de dtor niet-virtueel. In de geest van c, als iemand de manier waarop je dit bedoeld had echt wil negeren, waarom zou je hem dan stoppen? (Ik heb ook nooit het nut van finallessen/methoden in Java gezien).

//Note: this class is not designed to be extended. (Hence the non-virtual dtor)
struct DontExtened
{
  DontExtened();
  /*NOT VIRTUAL*/
  ~DontExtened();
  ...
};

Antwoord 4, autoriteit 4%

1) is een kwestie van smaak. Als ik het goed zie, verplaatsen je mooiere 2e en 3e oplossingen de fout in bepaalde omstandigheden van linktijd naar compileertijd, wat over het algemeen beter zou moeten zijn.

2) Virtuele overerving is nodig om de verantwoordelijkheid te forceren om de (virtuele) basisklasse te initialiseren naar de meest afgeleide klasse waarvan de basisklasse-ctor niet langer bereikbaar is.


Antwoord 5, autoriteit 4%

Om je vraag te beantwoorden, je kunt niet overerven van CBase omdat bij virtuele overerving een afgeleide klasse directe toegang zou moeten hebben tot de klasse waarvan deze virtueel is geërfd. In dit geval zou een klasse die afkomstig zou zijn van CBase, directe toegang tot CSealed moeten hebben, wat niet kan omdat de constructor privé is.

Hoewel ik het nut van dit alles niet inzie (dwz: overerving stoppen), kun je generaliseren met behulp van sjablonen (ik denk niet dat het compileert op alle compilers, maar wel met MSVC)

template<class T>
class CSealed
{
    friend T;    // Don't do friend class T because it won't compile
    CSealed() {}
};
class CBase : private virtual CSealed<CBase>
{
};

Antwoord 6

Als je kunt, zou ik voor de eerste optie gaan (private constructor). De reden is dat vrijwel elke ervaren C++-programmeur dat in één oogopslag zal zien en in staat zal zijn om te herkennen dat u subclassificatie probeert te voorkomen.

Er kunnen andere, meer lastige methoden zijn om subclassificatie te voorkomen, maar in dit geval hoe eenvoudiger, hoe beter.


Antwoord 7

Vanaf C++11 is er een schone oplossing die ik tot mijn verbazing hier niet zie. We kunnen de klasse definitief maken om verdere overerving te voorkomen.

class Foo final {};
class Bar: public Foo {}; // Fails to compile as Foo is marked as final

Antwoord 8

class myclass;
    class my_lock {
        friend class myclass;
    private:
        my_lock() {}
        my_lock(const my_lock&) {}
    };
    class myclass : public virtual my_lock {
        // ...
    public:
        myclass();
        myclass(char*);
        // ...
    };
    myclass m;
    class Der : public myclass { };
    Der dd;  // error Der::dd() cannot access
            // my_lock::my_lock(): private  member

Ik heb het hier gevonden om de eer te bewijzen. Ik post hier alleen andere mensen kunnen er gemakkelijk bij
http://www.devx.com/tips/Tip/38482


Antwoord 9

Om in te gaan op Francis’ antwoord: als klasse Bottomis afgeleid van klasse Middle, die vrijwel erft van klasse Top, is de meest afgeleide klasse (Bottom) die verantwoordelijk is voor het construeren van de virtueel geërfde basisklasse (Top). Anders zou de compiler in het scenario met meerdere overerving/diamant-van-dood (waar virtuele overerving klassiek wordt gebruikt) niet weten welke van de twee “middelste” klassen de enkele basisklasse zou moeten construeren. De aanroep van de Middle-constructor naar de constructor van Topwordt daarom genegeerd wanneer Middlewordt opgebouwd uit Bottom:

class Top {
    public:
        Top() {}
}
class Middle: virtual public Top {
    public:
        Middle(): Top() {} // Top() is ignored if Middle constructed through Bottom()
}
class Bottom: public Middle {
    public:
        Bottom(): Middle(), Top() {}
}

Dus, in de benadering 2) of 3) in uw vraag, kan Bottom()Top()niet aanroepen omdat het privé wordt geërfd (standaard, zoals in je code, maar het is de moeite waard om het expliciet te maken) in Middleen is dus niet zichtbaar in Bottom. (bron)

Other episodes