Это возможно!
Но что именно возможно, давайте сузим. Люди часто хотят какой-то «статической виртуальной функции» из-за дублирования кода, необходимого для возможности вызова той же функции через статический вызов SomeDerivedClass :: myfunction () и полиморфный вызов base_class_pointer-> myfunction (). «Правовой» метод для предоставления такой функциональности - дублирование определений функций:
class Object
{
public:
static string getTypeInformationStatic() { return "base class";}
virtual string getTypeInformation() { return getTypeInformationStatic(); }
};
class Foo: public Object
{
public:
static string getTypeInformationStatic() { return "derived class";}
virtual string getTypeInformation() { return getTypeInformationStatic(); }
};
Что, если базовый класс имеет большое количество статических функций, и производный класс должен переопределить каждую из них, и один забыл предоставить дублирующее определение для виртуальной функции. Да, во время runtime мы получим странную ошибку, которую трудно отследить. Потому что дублирование кода это плохо. Следующая попытка решить эту проблему (и я хочу сказать заранее, что она полностью безопасна для типов и не содержит никакой черной магии, такой как typeid или dynamic_cast's:)
Итак, мы хотим предоставить только одно определение getTypeInformation () для производного класса, и очевидно, что это должно быть определение статической функции, потому что невозможно вызвать "SomeDerivedClass :: getTypeInformation () ", если getTypeInformation () является виртуальным. Как мы можем вызвать статическую функцию производного класса через указатель на базовый класс? Это невозможно с vtable, потому что vtable сохраняет указатели только на виртуальные функции, и поскольку мы решили не использовать виртуальные функции, мы не можем изменять vtable в наших интересах. Затем, чтобы получить доступ к статической функции для производного класса через указатель на базовый класс, мы должны каким-то образом хранить тип объекта в его базовом классе. Один из подходов состоит в том, чтобы сделать базовый класс шаблонизированным с использованием «любопытно повторяющегося шаблона», но здесь это не подходит, и мы будем использовать технику, называемую «стирание типа»:
class TypeKeeper
{
public:
virtual string getTypeInformation() = 0;
};
template<class T>
class TypeKeeperImpl: public TypeKeeper
{
public:
virtual string getTypeInformation() { return T::getTypeInformationStatic(); }
};
Теперь мы можем хранить тип объекта в базовом классе «Object» с переменной «keeper»:
class Object
{
public:
Object(){}
boost::scoped_ptr<TypeKeeper> keeper;
//not virtual
string getTypeInformation() const
{ return keeper? keeper->getTypeInformation(): string("base class"); }
};
В производном классе хранитель должен быть инициализирован во время построения:
class Foo: public Object
{
public:
Foo() { keeper.reset(new TypeKeeperImpl<Foo>()); }
//note the name of the function
static string getTypeInformationStatic()
{ return "class for proving static virtual functions concept"; }
};
Давайте добавим синтаксический сахар:
template<class T>
void override_static_functions(T* t)
{ t->keeper.reset(new TypeKeeperImpl<T>()); }
#define OVERRIDE_STATIC_FUNCTIONS override_static_functions(this)
Теперь объявления потомков выглядят так:
class Foo: public Object
{
public:
Foo() { OVERRIDE_STATIC_FUNCTIONS; }
static string getTypeInformationStatic()
{ return "class for proving static virtual functions concept"; }
};
class Bar: public Foo
{
public:
Bar() { OVERRIDE_STATIC_FUNCTIONS; }
static string getTypeInformationStatic()
{ return "another class for the same reason"; }
};
использование:
Object* obj = new Foo();
cout << obj->getTypeInformation() << endl; //calls Foo::getTypeInformationStatic()
obj = new Bar();
cout << obj->getTypeInformation() << endl; //calls Bar::getTypeInformationStatic()
Foo* foo = new Bar();
cout << foo->getTypeInformation() << endl; //calls Bar::getTypeInformationStatic()
Foo::getTypeInformation(); //compile-time error
Foo::getTypeInformationStatic(); //calls Foo::getTypeInformationStatic()
Bar::getTypeInformationStatic(); //calls Bar::getTypeInformationStatic()
Преимущества:
- меньше дублирования кода (но мы
должен позвонить
OVERRIDE_STATIC_FUNCTIONS в каждом
конструктор)
Недостатки:
- OVERRIDE_STATIC_FUNCTIONS в каждом
Конструктор
- память и производительность
накладные расходы
- повышенной сложности
Открытые выпуски:
1) существуют разные имена для статических и виртуальных функций
как решить двусмысленность здесь?
class Foo
{
public:
static void f(bool f=true) { cout << "static";}
virtual void f() { cout << "virtual";}
};
//somewhere
Foo::f(); //calls static f(), no ambiguity
ptr_to_foo->f(); //ambiguity
2) как неявно вызвать OVERRIDE_STATIC_FUNCTIONS внутри каждого конструктора?