C++ статические виртуальные члены?
Возможно ли в C++ иметь функцию-член, которая одновременно static
а также virtual
? Видимо, не существует простого способа сделать это (static virtual member();
это ошибка компиляции), но есть ли хотя бы способ добиться того же эффекта?
IE:
struct Object
{
struct TypeInformation;
static virtual const TypeInformation &GetTypeInformation() const;
};
struct SomeObject : public Object
{
static virtual const TypeInformation &GetTypeInformation() const;
};
Имеет смысл использовать GetTypeInformation()
как по экземпляру (object->GetTypeInformation()
) и по классу (SomeObject::GetTypeInformation()
), что может быть полезно для сравнения и жизненно важно для шаблонов.
Единственный способ, которым я могу придумать, - это написать две функции / функцию и константу для каждого класса или использовать макросы.
Любые другие решения?
20 ответов
Нет, это невозможно сделать, так как то, что произойдет, когда вы позвоните Object::GetTypeInformation()
? Он не может знать, какую версию производного класса вызывать, поскольку с ним не связано ни одного объекта.
Вы должны будете сделать это нестатической виртуальной функцией для правильной работы; если вы также хотите иметь возможность вызывать версию определенного производного класса не виртуально без экземпляра объекта, вам придется также предоставить вторую избыточную статическую не виртуальную версию.
Многие говорят, что это невозможно, я бы пошел еще дальше и сказал, что это не имеет смысла.
Статический член - это то, что не относится ни к одному экземпляру, а только к классу.
Виртуальный член - это то, что не имеет прямого отношения ни к какому классу, а только к экземпляру.
Таким образом, статический виртуальный член будет чем-то, что не относится ни к какому экземпляру или какому-либо классу.
Я столкнулся с этой проблемой на днях: у меня было несколько классов, полных статических методов, но я хотел использовать наследование и виртуальные методы и уменьшить повторение кода. Мое решение было:
Вместо того чтобы использовать статические методы, используйте синглтон с виртуальными методами.
Другими словами, каждый класс должен содержать статический метод, который вы вызываете, чтобы получить указатель на один общий экземпляр класса. Вы можете сделать настоящие конструкторы частными или защищенными, чтобы внешний код не мог их использовать неправильно, создавая дополнительные экземпляры.
На практике использование синглтона во многом похоже на использование статических методов, за исключением того, что вы можете использовать наследование и виртуальные методы.
Хотя Alsk уже дал довольно подробный ответ, я хотел бы добавить альтернативу, поскольку я думаю, что его расширенная реализация слишком сложна.
Мы начнем с абстрактного базового класса, который предоставляет интерфейс для всех типов объектов:
class Object
{
public:
virtual char* GetClassName() = 0;
};
Теперь нам нужна актуальная реализация. Но чтобы избежать необходимости писать как статические, так и виртуальные методы, наши классы объектов будут наследовать виртуальные методы. Очевидно, это работает только в том случае, если базовый класс знает, как получить доступ к статической функции-члену. Поэтому нам нужно использовать шаблон и передать ему фактическое имя класса объектов:
template<class ObjectType>
class ObjectImpl : public Object
{
public:
virtual char* GetClassName()
{
return ObjectType::GetClassNameStatic();
}
};
Наконец нам нужно реализовать наш реальный объект (ы). Здесь нам нужно только реализовать статическую функцию-член, виртуальные функции-члены будут унаследованы от шаблонного класса ObjectImpl, экземпляр которого содержит имя производного класса, поэтому он получит доступ к своим статическим членам.
class MyObject : public ObjectImpl<MyObject>
{
public:
static char* GetClassNameStatic()
{
return "MyObject";
}
};
class YourObject : public ObjectImpl<YourObject>
{
public:
static char* GetClassNameStatic()
{
return "YourObject";
}
};
Давайте добавим немного кода для тестирования:
char* GetObjectClassName(Object* object)
{
return object->GetClassName();
}
int main()
{
MyObject myObject;
YourObject yourObject;
printf("%s\n", MyObject::GetClassNameStatic());
printf("%s\n", myObject.GetClassName());
printf("%s\n", GetObjectClassName(&myObject));
printf("%s\n", YourObject::GetClassNameStatic());
printf("%s\n", yourObject.GetClassName());
printf("%s\n", GetObjectClassName(&yourObject));
return 0;
}
Это возможно!
Но что именно возможно, давайте сузим. Люди часто хотят какую-то "статическую виртуальную функцию" из-за дублирования кода, необходимого для возможности вызова той же функции через статический вызов "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(); }
};
Что, если базовый класс имеет большое количество статических функций, а производный класс должен переопределить каждую из них, и один из них забыл предоставить дублирующее определение для виртуальной функции. Да, во время выполнения мы получим странную ошибку, которую трудно отследить. Потому что дублирование кода это плохо. Следующая попытка решить эту проблему (и я хочу сказать заранее, что она полностью безопасна от типов и не содержит никакой черной магии, такой как 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 внутри каждого конструктора?
Это возможно. Сделайте две функции: статическую и виртуальную
struct Object{
struct TypeInformation;
static const TypeInformation &GetTypeInformationStatic() const
{
return GetTypeInformationMain1();
}
virtual const TypeInformation &GetTypeInformation() const
{
return GetTypeInformationMain1();
}
protected:
static const TypeInformation &GetTypeInformationMain1(); // Main function
};
struct SomeObject : public Object {
static const TypeInformation &GetTypeInformationStatic() const
{
return GetTypeInformationMain2();
}
virtual const TypeInformation &GetTypeInformation() const
{
return GetTypeInformationMain2();
}
protected:
static const TypeInformation &GetTypeInformationMain2(); // Main function
};
Это невозможно, но это только потому, что упущение. Это не то, что "не имеет смысла", как утверждают многие люди. Чтобы было ясно, я говорю о чем-то вроде этого:
struct Base {
static virtual void sayMyName() {
cout << "Base\n";
}
};
struct Derived : public Base {
static void sayMyName() override {
cout << "Derived\n";
}
};
void foo(Base *b) {
b->sayMyName();
Derived::sayMyName(); // Also would work.
}
Это на 100% то, что может быть реализовано (просто нет), и я бы сказал, что это полезно.
Посмотрите, как работают обычные виртуальные функции. Удалить static
S и добавить в некоторые другие вещи, и у нас есть:
struct Base {
virtual void sayMyName() {
cout << "Base\n";
}
virtual void foo() {
}
int somedata;
};
struct Derived : public Base {
void sayMyName() override {
cout << "Derived\n";
}
};
void foo(Base *b) {
b->sayMyName();
}
Это работает нормально, и в основном происходит то, что компилятор создает две таблицы, называемые VTables, и присваивает индексы виртуальным функциям следующим образом.
enum Base_Virtual_Functions {
sayMyName = 0;
foo = 1;
};
using VTable = void*[];
const VTable Base_VTable = {
&Base::sayMyName,
&Base::foo
};
const VTable Derived_VTable = {
&Derived::sayMyName,
&Base::foo
};
Затем каждый класс с виртуальными функциями дополняется другим полем, которое указывает на его VTable, поэтому компилятор в основном изменяет их так:
struct Base {
VTable* vtable;
virtual void sayMyName() {
cout << "Base\n";
}
virtual void foo() {
}
int somedata;
};
struct Derived : public Base {
VTable* vtable;
void sayMyName() override {
cout << "Derived\n";
}
};
Тогда что на самом деле происходит, когда вы звоните b->sayMyName()
? В основном это:
b->vtable[Base_Virtual_Functions::sayMyName](b);
(Первый параметр становится this
.)
Хорошо, хорошо, так как это будет работать со статическими виртуальными функциями? Ну, в чем разница между статическими и нестатическими функциями-членами? Разница лишь в том, что последние получают this
указатель.
Мы можем сделать то же самое со статическими виртуальными функциями - просто удалите this
указатель.
b->vtable[Base_Virtual_Functions::sayMyName]();
Это может затем поддерживать оба синтаксиса:
b->sayMyName(); // Prints "Base" or "Derived"...
Base::sayMyName(); // Always prints "Base".
Так что игнорируйте всех скептиков. Это имеет смысл. Почему тогда не поддерживается? Я думаю, что это потому, что это имеет очень мало пользы и может даже немного запутать.
Единственное техническое преимущество перед обычной виртуальной функцией заключается в том, что вам не нужно передавать this
к функции, но я не думаю, что это будет иметь какое-либо измеримое значение для производительности.
Это означает, что у вас нет отдельной статической и нестатической функции для случаев, когда у вас есть экземпляр, и когда у вас нет экземпляра, но также может сбивать с толку тот факт, что он действительно "виртуален" только при использовании вызов экземпляра.
Нет, это невозможно, потому что статическим функциям-членам не хватает this
указатель. И статические члены (и функции, и переменные) сами по себе не являются членами класса. Они просто вызваны ClassName::member
и придерживаться спецификаторов доступа к классу. Их хранение определяется где-то за пределами класса; хранилище не создается каждый раз, когда вы создаете экземпляр объекта класса. Указатели на членов класса отличаются семантикой и синтаксисом. Указатель на статический член является нормальным указателем во всех отношениях.
виртуальные функции в классе нуждаются в this
указатель, и очень связан с классом, следовательно, они не могут быть статическими.
Ну, довольно поздний ответ, но это возможно с использованием любопытно повторяющегося шаблона. Эта статья в Википедии содержит необходимую информацию, а также пример статического полиморфизма - это то, что вас просят.
Этому вопросу более десяти лет, но похоже, что он получает хороший объем трафика, поэтому я хотел опубликовать альтернативу с использованием современных функций C++, которых я больше нигде не видел.
Это решение использует CRTP и SFINAE для выполнения статической диспетчеризации. Само по себе в этом нет ничего нового, но во всех подобных реализациях, которые я обнаружил, отсутствует строгая проверка подписи для «переопределений». Эта реализация требует, чтобы сигнатура «переопределяющего» метода точно соответствовала сигнатуре «переопределенного» метода. Это поведение более похоже на поведение виртуальных функций, а также позволяет нам эффективно перегружать и «переопределять» статический метод.
Обратите внимание, что я заключил переопределение в кавычки, потому что, строго говоря, мы технически ничего не переопределяем. Вместо этого мы вызываем метод диспетчеризации X с подписью Y, который перенаправляет все свои аргументы в T :: X, где T относится к первому типу среди списка типов, так что T :: X существует с подписью Y. Этот список типов, рассматриваемых для диспетчеризации, могут быть любыми, но обычно включают класс реализации по умолчанию и производный класс.
Реализация
#include <experimental/type_traits>
template <template <class...> class Op, class... Types>
struct dispatcher;
template <template <class...> class Op, class T>
struct dispatcher<Op, T> : std::experimental::detected_t<Op, T> {};
template <template <class...> class Op, class T, class... Types>
struct dispatcher<Op, T, Types...>
: std::experimental::detected_or_t<
typename dispatcher<Op, Types...>::type, Op, T> {};
// Helper to convert a signature to a function pointer
template <class Signature> struct function_ptr;
template <class R, class... Args> struct function_ptr<R(Args...)> {
using type = R (*)(Args...);
};
// Macro to simplify creation of the dispatcher
// NOTE: This macro isn't smart enough to handle creating an overloaded
// dispatcher because both dispatchers will try to use the same
// integral_constant type alias name. If you want to overload, do it
// manually or make a smarter macro that can somehow put the signature in
// the integral_constant type alias name.
#define virtual_static_method(name, signature, ...) \
template <class VSM_T> \
using vsm_##name##_type = std::integral_constant< \
function_ptr<signature>::type, &VSM_T::name>; \
\
template <class... VSM_Args> \
static auto name(VSM_Args&&... args) \
{ \
return dispatcher<vsm_##name##_type, __VA_ARGS__>::value( \
std::forward<VSM_Args>(args)...); \
}
Пример использования
#include <iostream>
template <class T>
struct Base {
// Define the default implementations
struct defaults {
static std::string alpha() { return "Base::alpha"; };
static std::string bravo(int) { return "Base::bravo"; }
};
// Create the dispatchers
virtual_static_method(alpha, std::string(void), T, defaults);
virtual_static_method(bravo, std::string(int), T, defaults);
static void where_are_the_turtles() {
std::cout << alpha() << std::endl; // Derived::alpha
std::cout << bravo(1) << std::endl; // Base::bravo
}
};
struct Derived : Base<Derived> {
// Overrides Base::alpha
static std::string alpha(){ return "Derived::alpha"; }
// Does not override Base::bravo because signatures differ (even though
// int is implicitly convertible to bool)
static std::string bravo(bool){ return "Derived::bravo"; }
};
int main() {
Derived::where_are_the_turtles();
}
Я думаю, что вы пытаетесь сделать, можно сделать с помощью шаблонов. Я пытаюсь читать между строк здесь. То, что вы пытаетесь сделать, - это вызвать метод из некоторого кода, где он вызывает производную версию, но вызывающая сторона не указывает, какой класс. Пример:
class Foo {
public:
void M() {...}
};
class Bar : public Foo {
public:
void M() {...}
};
void Try()
{
xxx::M();
}
int main()
{
Try();
}
Вы хотите, чтобы Try() вызывал Bar-версию M без указания Bar. То, как вы делаете это для статики, это использование шаблона. Так что измените это так:
class Foo {
public:
void M() {...}
};
class Bar : public Foo {
public:
void M() {...}
};
template <class T>
void Try()
{
T::M();
}
int main()
{
Try<Bar>();
}
Нет, статическая функция-член не может быть виртуальной. Так как виртуальная концепция разрешается во время выполнения с помощью vptr, а vptr не является статическим членом класса. Из-за того, что статическая функция-член не может получить доступ к vptr, статический член может не быть виртуальным.
Нет, это невозможно, так как статические члены связаны во время компиляции, в то время как виртуальные члены связаны во время выполнения.
Если вы желаете использовать для
virtual static
чтобы иметь возможность определять интерфейс над статическим разделом класса, тогда есть решение вашей проблемы с использованием C++20
concept
с.
class ExBase { //object properties
public: virtual int do(int) = 0;
};
template <typename T> //type properties
concept ExReq = std::derived_from<T, ExBase> && requires(int i) { //~constexpr bool
{
T::do_static(i) //checks that this compiles
} -> std::same_as<int> //checks the expression type is int
};
class ExImpl : virtual public ExBase { //satisfies ExReq
public: int do(int i) override {return i;} //overrides do in ExBase
public: static int do_static(int i) {return i;} //satisfies ExReq
};
//...
void some_func(ExReq auto o) {o.do(0); decltype(o)::do_static(0);}
(это работает точно так же и с участниками!)
Подробнее о том, как работают концепции: https://en.cppreference.com/w/cpp/language/constraints.
Стандартные концепции, добавленные в C++20: https://en.cppreference.com/w/cpp/concepts
Оно существует, но...
Если вам нужен статический, виртуальный файл, то он у вас уже есть — просто не используйте .
Отказ от ответственности: что такое статическая виртуальная
Я чувствую, что программисты C++, не знакомые с этим термином, вводят его в заблуждение в других языках. Например, в Python «статический виртуальный» () — это виртуальный метод, который не принимаетself
(), но его по-прежнему можно вызывать - виртуально - из экземпляра объекта. Конечно, указатель должен был существовать в какой-то момент вызова функции, чтобы получить v-таблицу, и его отсутствие (если таковое имеется) является чисто синтаксическим сахаром.
Почему это не явно
C++ неявно включается в аргументы функции, поэтому нет смысла вводить эту функцию. Другими словами, не было бы никакой разницы между явно объявленным@staticmethod
например, Python и обычная функция C++, которая просто не использует расширение ".
Можно представить себе прирост производительности, если взятьthis
из аргументов функции, когда она не нужна, но на этом этапе мы будем догадываться, имеет ли это какое-либо значение, учитывая, что она уже используется, и удаляет ли компилятор уже неиспользуемые аргументы.
Во-первых, ответы верны: то, что запрашивает OP, является противоречием в терминах: виртуальные методы зависят от типа времени выполнения экземпляра; статические функции конкретно не зависят от экземпляра - просто от типа. Тем не менее, имеет смысл, чтобы статические функции возвращали что-то определенное для типа. Например, у меня было семейство классов MouseTool для шаблона State, и я начал, чтобы у каждого была статическая функция, возвращающая модификатор клавиатуры, который шел вместе с ним; Я использовал те статические функции в фабричной функции, которая сделала правильный экземпляр MouseTool. Эта функция проверяет состояние мыши с помощью MouseToolA::keyboardModifier(), MouseToolB::keyboardModifier() и т. Д., А затем создает соответствующий экземпляр. Конечно, позже я захотел проверить правильность состояния, поэтому я хотел написать что-то вроде "if (keyboardModifier == dynamic_type(*state)::keyboardModifier())" (не настоящий синтаксис C++), о чем этот вопрос задает,
Так что, если вы обнаружите, что хотите этого, вы можете изменить свое решение. Тем не менее, я понимаю желание иметь статические методы и затем вызывать их динамически в зависимости от динамического типа экземпляра. Я думаю, что шаблон посетителя может дать вам то, что вы хотите. Это дает вам то, что вы хотите. Это немного лишний код, но он может быть полезен для других посетителей.
Смотрите: http://en.wikipedia.org/wiki/Visitor_pattern для фона.
struct ObjectVisitor;
struct Object
{
struct TypeInformation;
static TypeInformation GetTypeInformation();
virtual void accept(ObjectVisitor& v);
};
struct SomeObject : public Object
{
static TypeInformation GetTypeInformation();
virtual void accept(ObjectVisitor& v) const;
};
struct AnotherObject : public Object
{
static TypeInformation GetTypeInformation();
virtual void accept(ObjectVisitor& v) const;
};
Тогда для каждого конкретного объекта:
void SomeObject::accept(ObjectVisitor& v) const {
v.visit(*this); // The compiler statically picks the visit method based on *this being a const SomeObject&.
}
void AnotherObject::accept(ObjectVisitor& v) const {
v.visit(*this); // Here *this is a const AnotherObject& at compile time.
}
а затем определите базового посетителя:
struct ObjectVisitor {
virtual ~ObjectVisitor() {}
virtual void visit(const SomeObject& o) {} // Or = 0, depending what you feel like.
virtual void visit(const AnotherObject& o) {} // Or = 0, depending what you feel like.
// More virtual void visit() methods for each Object class.
};
Затем конкретный посетитель, который выбирает соответствующую статическую функцию:
struct ObjectVisitorGetTypeInfo {
Object::TypeInformation result;
virtual void visit(const SomeObject& o) {
result = SomeObject::GetTypeInformation();
}
virtual void visit(const AnotherObject& o) {
result = AnotherObject::GetTypeInformation();
}
// Again, an implementation for each concrete Object.
};
наконец, используйте это:
void printInfo(Object& o) {
ObjectVisitorGetTypeInfo getTypeInfo;
Object::TypeInformation info = o.accept(getTypeInfo).result;
std::cout << info << std::endl;
}
Заметки:
- Constness оставлен как упражнение.
- Вы вернули ссылку из статики. Если у вас нет синглтона, это сомнительно.
Если вы хотите избежать ошибок копирования-вставки, когда один из ваших методов посещения вызывает неправильную статическую функцию, вы можете использовать шаблонную вспомогательную функцию (которая сама по себе не может быть виртуальной) с таким шаблоном, как этот:
struct ObjectVisitorGetTypeInfo {
Object::TypeInformation result;
virtual void visit(const SomeObject& o) { doVisit(o); }
virtual void visit(const AnotherObject& o) { doVisit(o); }
// Again, an implementation for each concrete Object.
private:
template <typename T>
void doVisit(const T& o) {
result = T::GetTypeInformation();
}
};
С C++ вы можете использовать статическое наследование с методом crt. Например, он широко используется в шаблоне окна atl & wtl.
Смотрите https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
Проще говоря, у вас есть класс, который основан на шаблоне самого себя, как класс myclass: public myancestor. С этого момента класс myancestor теперь может вызывать вашу статическую функцию T::YourImpl.
Я просмотрел другие ответы, и ни один из них, похоже, не упоминает таблицы виртуальных функций (vtable), что объясняет, почему это невозможно.
Статическая функция внутри класса C++ компилируется во что-то, что фактически совпадает с любой другой функцией в обычном пространстве имен.
Другими словами, когда вы объявляете функцию, вы используете имя класса как пространство имен, а не объект (у которого есть экземпляр с некоторыми связанными данными).
Давайте быстро посмотрим на это...
// This example is the same as the example below
class ExampleClass
{
static void exampleFunction();
int someData;
};
// This example is the same as the example above
namespace ExampleClass
{
void exampleFunction();
// Doesn't work quite the same. Each instance of a class
// has independent data. Here the data is global.
int someData;
}
После этого и понимания того, что на самом деле представляет собой функция-член, мы можем теперь рассмотреть vtables.
Если вы объявляете какую-либо виртуальную функцию в классе, компилятор создает блок данных, который (обычно) предшествует другим членам данных. Этот блок данных содержит информацию о времени выполнения, которая сообщает программе во время выполнения, куда ей нужно перейти в памяти, чтобы выполнить правильную (виртуальную) функцию для каждого экземпляра класса, который может быть создан во время выполнения.
Важным моментом здесь является «блок данных». Чтобы этот блок данных существовал, он должен храниться как часть экземпляра объекта (класса). Если ваша функцияstatic
, то мы уже сказали, что он использует имя класса в качестве пространства имен. Нет объекта, связанного с этим вызовом функции.
Чтобы добавить немного больше деталей: статическая функция не имеет неявногоthis
указатель, указывающий на память, в которой находится объект. Поскольку у него этого нет, вы не можете перейти к месту в памяти и найти виртуальную таблицу для этого объекта. Таким образом, вы не можете выполнять диспетчеризацию виртуальных функций.
Я никоим образом не являюсь экспертом в разработке компиляторов, но понимание вещей, по крайней мере, на этом уровне детализации полезно и (надеюсь?) позволяет легко понять, почему (по крайней мере, на С++)static virtual
не имеет смысла и не может быть преобразовано компилятором во что-то осмысленное.
Может быть, вы можете попробовать мое решение ниже:
class Base {
public:
Base(void);
virtual ~Base(void);
public:
virtual void MyVirtualFun(void) = 0;
static void MyStaticFun(void) { assert( mSelf != NULL); mSelf->MyVirtualFun(); }
private:
static Base* mSelf;
};
Base::mSelf = NULL;
Base::Base(void) {
mSelf = this;
}
Base::~Base(void) {
// please never delete mSelf or reset the Value of mSelf in any deconstructors
}
class DerivedClass : public Base {
public:
DerivedClass(void) : Base() {}
~DerivedClass(void){}
public:
virtual void MyVirtualFun(void) { cout<<"Hello, it is DerivedClass!"<<endl; }
};
int main() {
DerivedClass testCls;
testCls.MyStaticFun(); //correct way to invoke this kind of static fun
DerivedClass::MyStaticFun(); //wrong way
return 0;
}
Как уже говорили другие, есть 2 важных элемента информации:
- здесь нет
this
указатель при выполнении статического вызова функции и this
Указатель указывает на структуру, где виртуальная таблица или thunk используются для поиска того, какой метод времени выполнения вызывать.
Статическая функция определяется во время компиляции.
Я показал этот пример кода в статических членах C++ в классе; это показывает, что вы можете вызвать статический метод с указателем null:
struct Foo
{
static int boo() { return 2; }
};
int _tmain(int argc, _TCHAR* argv[])
{
Foo* pFoo = NULL;
int b = pFoo->boo(); // b will now have the value 2
return 0;
}