Seite 1 von 1

[C++] Statische Vererbung sicherer machen

Verfasst: 03.07.2013, 19:24
von Krishty
Hi,

wir alle kennen dynamische Vererbung in C++. Falls nicht, hier noch eine schnelle Erklärung:
    class Base {
    public:
        virtual void method() { }
    };

    class Derived : public Base {
    public:
        void method() override { }
    };

    void foo(Base & something) {
        something.method(); //
Ruft entweder Base::method() auf oder Derived::method(); je nach Laufzeittyp
    }
Ich benutze in letzter Zeit fast ausschließlich statische Vererbung. Erklärung, falls nötig:
    class Base {
    public:
        void method() { } //
nicht virtuell!
    };

    class Derived : public Base {
    public:
        void method() { }
    };

    void foo(Base & something) {
        something.method(); //
Ruft Base::method() auf
    }

    void foo(Derived & something) {
        something.method(); //
Ruft Derived::method() auf
    }
Besonders gern kombiniere ich das mit dem Curiously Recurring Template Pattern:

    template <typename Implementation> class Interface {
    private:

        void methodA() { } //
darf die Implementierung überschreiben; muss sie aber nicht

        void methodB(int) { } // die ebenfalls

    public:

        void doIt() { //
Darf nicht überschrieben werden!
            static_cast<Implementation *>(this)->methodA();
            static_cast<Implementation *>(this)->methodB(123);
        }

};


Ironisch an der ganzen Sache ist, dass in C++ Laufzeitpolymorphismus statisch verifiziert wird, aber statischer Polymorphismus nicht:

In der Variante mit virtual kann ich durch override symbolisieren, dass ich die Methode überschreiben will; und das Programm wird nicht kompiliert, falls die überschriebene Methodensignatur nicht passt. Außerdem kann ich Überschreiben durch pur virtuelle Deklaration erzwingen (virtual = 0), oder eine Funktion via final unüberschreibbar machen.

Diese Möglichkeiten gibt es bei statischem Polymorphismus direkt nicht. Es gibt eine gewisse Menge von Hilfskonstrukten, durch die man das besser absichern kann – zum Beispiel den Test, ob die Implementierung auch tatsächlich von der Schnittstelle erbt (und nicht einfach irgendwas anderes in den Template-Parameter gesteckt wurde):

    static_assert(boost::is_base_of<Implementation, Derived>::value_type, "fffuuuuu");

Und was macht man um sicherzustellen, dass die abgeleitete Klasse die richtigen Methoden überschrieben hat? Es gibt zwar tausend Möglichkeiten, festzustellen, ob eine Klasse eine Methode bestimmten Namens anbietet – aber auch die Parameterliste für perfekte Überschreibung zu prüfen erscheint mir nicht mehr trivial. Ich breche mir darum alle Wochen mal wieder was, wenn eine Überladung nicht ganz passt und der Compiler ohne Mucken die falsche Funktion aufruft.

Was kann man also tun, um zu prüfen, ob eine nicht-virtuelle überschriebene Funktion in der Basisklasse existiert und zugreifbar ist (override), bzw. nicht existiert (new und final)?

tl;dr: Womit statischen Polymorphismus verifizieren?

Re: [C++] Statische Vererbung sicherer machen

Verfasst: 03.07.2013, 19:44
von CodingCat

Code: Alles auswählen

template <class A, class B>
struct is_equal { static bool const value = false; };
template <class A>
struct is_equal<A, A> { static bool const value = true; };

template <class Mem>
struct mem_decomp { typedef Mem type; };
template <class Mem, class Class>
struct mem_decomp<Mem Class::*> { typedef Mem type; typedef Class clazz; };

template <class Overrider, class Overridee>
struct override_checker
{
	static bool const value = // Vergleiche Signaturen
		is_equal<typename mem_decomp<Overrider>::type, typename mem_decomp<Overridee>::type>::value;
		// Erzwingt Benutzung in Methodenrumpf, was den Test in Templates aufgrund von spärlicher Instantiierung nutzlos macht
//		&& is_base_of<typename mem_decomp<Overridee>::clazz, typename mem_decomp<Overrider>::clazz>::value;
};

#define STATIC_OVERRIDE(overider, overidee) \
	static_assert(override_checker<decltype(&overider), decltype(&overidee)>::value, "static override");

struct base
{
	void overrideMe(int a);
};

struct derived1 : base
{
};

struct derived2 : base
{
	void overrideMe(int a);
	STATIC_OVERRIDE(overrideMe, base::overrideMe);
};

template <class T>
struct derived3 : base
{
	void overrideMe(float a);
	STATIC_OVERRIDE(overrideMe, base::overrideMe); // Fehler: 'static override'
};

int main()
{
	derived2 test1;
	derived3<int> test2;
	return 0;
}
Nachtrag: Jetzt mit einheitlichem Check für Templates und nicht-Templates.

Re: [C++] Statische Vererbung sicherer machen

Verfasst: 03.07.2013, 20:38
von CodingCat
So, nun zu abstrakten statischen Methoden.

Hilfskonstrukte:

Code: Alles auswählen

#include <type_traits>

template <class Mem>
struct mem_decomp { typedef Mem type; };
template <class Mem, class Class>
struct mem_decomp<Mem Class::*> { typedef Mem type; typedef Class clazz; };

template <class Overrider, class Overridee>
struct signature_checker
{
	static bool const value = std::is_same<typename mem_decomp<Overrider>::type, typename mem_decomp<Overridee>::type>::value;
};
template <class Overrider, class Overridee>
struct override_checker
{
	static bool const value = signature_checker<Overrider, Overridee>::value
		&& !std::is_same<typename mem_decomp<Overridee>::clazz, typename mem_decomp<Overrider>::clazz>::value
		&& std::is_base_of<typename mem_decomp<Overridee>::clazz, typename mem_decomp<Overrider>::clazz>::value;
};

#define STATIC_OVERRIDE(overider, overidee) \
	static_assert(signature_checker<decltype(&overider), decltype(&overidee)>::value, "static override mismatch");
#define STATICALLY_ABSTRACT(overider, overidee) \
	static_assert(override_checker<decltype(&overider), decltype(&overidee)>::value, "statically abstract");
#define STATIC_THIS static_cast<StaticType*>(this)

#define STATICALLY_ABSTRACT_METHODS static void is_statically_abstract()

template <class Base>
struct statically_abstract
{
	statically_abstract()
	{
		Base::is_statically_abstract();
	}
};
Benutzung:

Code: Alles auswählen

template <class StaticType>
struct base : statically_abstract< base<StaticType> >
{
	void overrideMe(int a);
	
	STATICALLY_ABSTRACT_METHODS
	{
		STATICALLY_ABSTRACT(StaticType::overrideMe, overrideMe);
		// Error: 'statically abstract' while compiling class template member function 'void base<derived1>::is_statically_abstract(void)'
	}
	
	void doIt(int a)
	{
		STATIC_THIS->overrideMe(a);
	}
};

struct derived1 : base<derived1>
{
};

struct derived2 : base<derived2>
{
	void overrideMe(int a) { }
	STATIC_OVERRIDE(overrideMe, base::overrideMe);
};

template <class T>
struct derived3 : base< derived3<T> >
{
	void overrideMe(float a) { }
	STATIC_OVERRIDE(overrideMe, base::overrideMe); // Error: 'static override mismatch'
};

int main()
{
	derived1 test1;
	derived2 test2;
	derived3<int> test3;
	return 0;
}
Statische Abstraktheit wird dabei zum gleichen Zeitpunkt geprüft wie bei dynamischer Abstraktheit, also bei der resten Objektinstantiierung.