virtuelle Funktionen implementieren



  • Huhu.

    Ich habe mal aus Spaß virtuelle Funktionen selbst implementiert. Allerdings habe ich gerade keine Idee, wie man beliebige Funktionssignaturen unterstützen kann. Am besten seht ihr euch einfach direkt meinen Ansatz an.

    #ifndef VIRTUAL_HPP
    #define VIRTUAL_HPP
    
    #define VIRTUAL_OBJECT public virtual_base
    #define VIRTUAL_INIT(func) register_virtual(#func, &virtual_##func##_wrapper);
    #define VIRTUAL_DESTRUCTOR_INIT(type) register_virtual("virtual_destructor_function", &type::virtual_destructor_function);
    #define VIRTUAL_DELETE(obj) (obj)->invoke("virtual_destructor_function");
    #define VIRTUAL_INVOKE(obj, func) (obj)->invoke(#func);
    
    #define VIRTUAL_FUNCTION(func, type)                            \
    	static void virtual_##func##_wrapper(virtual_base* obj) \
    	{                                                       \
    		static_cast<type*>(obj)->func();                \
    	}
    
    #define VIRTUAL_EMPTY_DESTRUCTOR(type)                             \
    	static void virtual_destructor_function(virtual_base* obj) \
    	{                                                          \
    		delete static_cast<type*>(obj);                    \
    	}                                                          \
    		                                                   \
    	~type() {}
    
    #define VIRTUAL_DESTRUCTOR_BEGIN(type)                             \
    	static void virtual_destructor_function(virtual_base* obj) \
    	{                                                          \
    		delete static_cast<type*>(obj);                    \
    	}                                                          \
    	                                                           \
    	~type()                                                    \
    	{
    
    #define VIRTUAL_DESTRUCTOR_END \
    	}
    
    #include <map>
    
    class virtual_base
    {
    	std::map<std::string, void (*) (virtual_base*)> vtable;
    
    protected:
    	VIRTUAL_EMPTY_DESTRUCTOR(virtual_base)
    
    	virtual_base()
    	{
    		VIRTUAL_DESTRUCTOR_INIT(virtual_base)
    	}
    
    	void register_virtual(const std::string& funcname, void (*function) (virtual_base*))
    	{
    		vtable[funcname] = function;
    	}
    
    public:
    	void invoke(const std::string& funcname)
    	{
    		vtable.find(funcname)->second(this);
    	}
    };
    
    #endif // VIRTUAL_HPP
    

    Verwendet man dann so:

    #include <iostream>
    
    #include "virtual.hpp"
    
    struct base : VIRTUAL_OBJECT
    {
    	base()
    	{
    		VIRTUAL_INIT(say_hello)
    		VIRTUAL_DESTRUCTOR_INIT(base)
    	}
    
    	void say_hello()
    	{
    		std::cout << "Hello, World! base!" << std::endl;
    	}
    
    	VIRTUAL_FUNCTION(say_hello, base)
    
    	VIRTUAL_DESTRUCTOR_BEGIN(base)
    	{
    		std::cout << "base::~base()" << std::endl;
    	}
    	VIRTUAL_DESTRUCTOR_END
    };
    
    struct derived : public base
    {
    	derived()
    	{
    		VIRTUAL_INIT(say_hello)
    		VIRTUAL_DESTRUCTOR_INIT(derived)
    	}
    
    	void say_hello()
    	{
    		std::cout << "Hello, World! derived!" << std::endl;
    	}
    
    	VIRTUAL_FUNCTION(say_hello, derived)
    
    	VIRTUAL_DESTRUCTOR_BEGIN(derived)
    	{
    		std::cout << "derived::~derived()" << std::endl;
    	}
    	VIRTUAL_DESTRUCTOR_END
    };
    
    int main()
    {
    	derived* d = new derived;
    
    	base* b = d;
    	VIRTUAL_INVOKE(b, say_hello)
    	VIRTUAL_DELETE(b)
    
    	return 0;
    }
    

    Grüße,
    PI



  • Eine map ist der falsche Ansatz.
    Ich habe ein primitives System mal in C geschrieben. Das konnte Vererbung über Copy&Paste und man konnte nicht die Basisklassen Implementierung einer virtuellen Funktion aufrufen...

    Das ganze lief über Funktionszeiger als Member der struct. Ein call sah so aus:

    object->function(object);
    

    was natürlich etwas dämlich ist, aber für mein Projekt hat das damals gereicht.

    #include<iostream>
    #include<cstdlib>
    using namespace std;
    
    #define MAKE_THAT(type) type* that=(type*)o
    #define THAT void* o
    
    struct Animal {
       void (*make_noise)(THAT);
       void (*destroy)(THAT);
    };
    void animal_make_noise(THAT) { cout<<"pure virtual function called\n"; exit(-1); }
    void animal_destroy(THAT) {
    	MAKE_THAT(Animal);
    	delete that;
    }
    Animal* animal_create() {
       Animal* p = new Animal();
       p->make_noise=animal_make_noise;
       p->destroy=animal_destroy;
       return p;
    }
    
    struct Dog {
       void (*make_noise)(THAT);
       void (*destroy)(THAT);
    }; //copy&paste inheritance
    void dog_make_noise(THAT) {
    	MAKE_THAT(Dog);
       	cout<<"bar bark\n";
    }
    void dog_destroy(THAT) {
    	MAKE_THAT(Dog);
    	delete that;
    }
    Dog* dog_create() {
       Dog* p=new Dog();
       p->make_noise = dog_make_noise;
       p->destroy=dog_destroy;
       return p;
    }
    
    struct Cat {
       void (*make_noise)(THAT);
       void (*destroy)(THAT);
    }; //copy&paste inheritance
    void cat_make_noise(THAT) {
    	MAKE_THAT(Cat);
    	cout<<"meouw\n";
    }
    void cat_destroy(THAT) {
    	MAKE_THAT(Cat);
    	delete that;
    }
    Cat* cat_create() {
       Cat* p=new Cat();
       p->make_noise = cat_make_noise;
       p->destroy=cat_destroy;
       return p;
    }
    
    int main() {
    	Animal* a = (Animal*)dog_create();
    	a->make_noise(a);
    	a->destroy(a);
    
    	a=(Animal*)cat_create();
    	a->make_noise(a);
    	a->destroy(a);
    }
    

    So sah das ganze damals etwa aus. Vielleicht ist das ein Ansatz? Auch wenn es sehr limitiert ist...

    PS:
    Die Objekt Erstellung ging anders. Die ging in etwa so:

    Dog* dog_create(THAT) {
       MAKE_THAT(Dog);
       if(!that) that=new Dog;
       that = animal_create((Animal*)that);
       that->make_noise = dog_make_noise;
       that->destroy = dog_destroy;
    }
    

    so dass die ctors gechained waren und man somit die initialisierung von basisklassen korrekt hatte.



  • Dein Code ist aber irgendwie eine C/C++ Mischung 😉

    Nunja, egal. Die Idee mit den Memberfunktionszeigern sollte genau das sein, was ich brauche. Werde das mal versuchen, danke 👍



  • Hi 314159265358979,

    hier ist mein C++-Ansatz:

    #include <iostream>
    
    template<typename TObject, typename TVTable>
    struct VTableObject{
    	VTableObject(TObject& object, const TVTable& vtable) : object(object), vtable(vtable) {}
    	TObject& object;
    	const TVTable& vtable;
    };
    
    class Animal{};
    
    struct AnimalVTable{
    	void (*say_hello)(const Animal& animal);
    };
    
    static void hello_cat(const Animal& animal);
    static void hello_dog(const Animal& animal);
    class Cat;
    class Dog;
    class CatVTable;
    class DogVTable;
    
    static CatVTable& cat_vtable_instance(); // als Funktion, da sonst 2 Dateien notwendig
    static DogVTable& dog_vtable_instance();
    
    class Cat : public Animal{
    public:
    	typedef VTableObject<Cat, CatVTable> vtable_object_type;
    	typedef VTableObject<const Cat, CatVTable> vtable_const_object_type;
    
    	vtable_object_type vtable_object() { return vtable_object_type(*this, cat_vtable_instance()); }
    	vtable_const_object_type vtable_object() const { return vtable_const_object_type(*this, cat_vtable_instance()); }
    	void miau() const { std::cout << "miau" << std::endl; }
    };
    
    class Dog : public Animal{
    public:
    	typedef VTableObject<Dog, DogVTable> vtable_object_type;
    	typedef VTableObject<const Dog, DogVTable> vtable_const_object_type;
    
    	vtable_object_type vtable_object() { return vtable_object_type(*this, dog_vtable_instance()); }
    	vtable_const_object_type vtable_object() const { return vtable_const_object_type(*this, dog_vtable_instance()); }
    	void wuff() const { std::cout << "wuff" << std::endl; }
    };
    
    struct CatVTable{
    	AnimalVTable animal_v_table;
    
    	VTableObject<Animal, AnimalVTable> cast_to_animal(Cat& cat) const{ return VTableObject<Animal, AnimalVTable>(cat, animal_v_table); }
    	VTableObject<const Animal, AnimalVTable> cast_to_animal(const Cat& cat) const{ return VTableObject<const Animal, AnimalVTable>(cat, animal_v_table); }
    
    	CatVTable(){
    		animal_v_table.say_hello = &hello_cat;
    	}
    };
    
    struct DogVTable{
    	AnimalVTable animal_v_table;
    
    	VTableObject<Animal, AnimalVTable> cast_to_animal(Dog& dog) const{ return VTableObject<Animal, AnimalVTable>(dog, animal_v_table); }
    	VTableObject<const Animal, AnimalVTable> cast_to_animal(const Dog& dog) const{ return VTableObject<const Animal, AnimalVTable>(dog, animal_v_table); }
    
    	DogVTable(){
    		animal_v_table.say_hello = &hello_dog;
    	}
    };
    
    static CatVTable& cat_vtable_instance(){
    	static CatVTable cat_vtable;
    	return cat_vtable;
    }
    static DogVTable& dog_vtable_instance(){
    	static DogVTable dog_vtable;
    	return dog_vtable;
    }
    
    static void hello_cat(const Animal& animal){
    	static_cast<const Cat&>(animal).miau();
    }
    
    static void hello_dog(const Animal& animal){
    	static_cast<const Dog&>(animal).wuff();
    }
    
    static void say_hello(VTableObject<const Animal, AnimalVTable>& vtable_object){
    	(vtable_object.vtable.say_hello)(vtable_object.object);
    }
    
    template<typename T, typename T2>
    static void say_hello(VTableObject<T, T2>& vto){
    	VTableObject<const Animal, AnimalVTable> animal_vto = vto.vtable.cast_to_animal(vto.object);
    	say_hello(animal_vto);
    }
    
    template<typename T>
    static void say_hello(const T& t){
    	typename T::vtable_const_object_type vto = t.vtable_object();
    	say_hello(vto);
    }
    
    int main(){
    	Cat cat;
    	Dog dog;
    	say_hello(cat);
    	say_hello(dog);
    }
    

    Ich glaube er ist etwas flexiblerer.
    Da sieht man mal, was der Compiler einem an Schreibarbeit abnimmt 😮

    Gruß,
    XSpille



  • Hm, vielleicht habe ich vergessen zu erwähnen, dass mein Ziel ist, dass man die virtuellen Funktionen möglichst ähnlich den echten schreiben kann. 😉



  • 314159265358979 schrieb:

    Hm, vielleicht habe ich vergessen zu erwähnen, dass mein Ziel ist, dass man die virtuellen Funktionen möglichst ähnlich den echten schreiben kann. 😉

    Ich dachte du wolltest die internen Vorgänge mal nachbauen 🙂
    Und ich vermute, dass es (auch wenn der Standard nichts vorschreibt) i. d. R. vor Optimierungen, etwa so umgesetzt ist.


Anmelden zum Antworten