C ++ статические виртуальные члены? - PullRequest
129 голосов
/ 30 ноября 2009

Возможно ли в C ++ иметь функцию-член как static, так и virtual? По-видимому, не существует простого способа сделать это (static virtual member(); - ошибка компиляции), но есть ли хотя бы способ добиться того же эффекта?

т.е:

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()), что может быть полезно для сравнения и жизненно важно для шаблонов.

Единственный способ, которым я могу придумать, - это написать две функции / функцию и константу для каждого класса или использовать макросы.

Есть ли другие решения?

Ответы [ 16 ]

0 голосов
/ 09 ноября 2018

С c ++ вы можете использовать статическое наследование с методом crt. Например, он широко используется в шаблоне окна atl & wtl.

См. https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

Проще говоря, у вас есть класс, который создан из самого себя, как класс myclass: public myancestor. С этого момента класс myancestor теперь может вызывать вашу статическую функцию T :: YourImpl.

0 голосов
/ 05 октября 2018

Это невозможно, но это только потому, что упущение. Это не то, что «не имеет смысла», как утверждают многие люди. Чтобы было ясно, я говорю о чем-то вроде этого:

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 в функцию, но я не думаю, что это могло бы повлиять на производительность.

Это означает, что у вас нет отдельной статической и нестатической функции для случаев, когда у вас есть экземпляр, и когда у вас нет экземпляра, но также может сбить с толку тот факт, что он действительно «виртуальный» когда вы используете вызов экземпляра.

0 голосов
/ 20 августа 2013

Во-первых, ответы верны, что то, что запрашивает 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;
}

Примечания:

  • Константность оставлена ​​как упражнение.
  • Вы вернули ссылку из статики. Если у вас нет синглтона, это сомнительно.

Если вы хотите избежать ошибок копирования и вставки, когда один из ваших методов посещения вызывает неправильную статическую функцию, вы можете использовать шаблонную вспомогательную функцию (которая не может быть виртуальной) для вашего посетителя с таким шаблоном:

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();
    }
};
0 голосов
/ 24 марта 2011

Может быть, вы можете попробовать мое решение ниже:

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;
}
0 голосов
/ 30 ноября 2009

Как уже говорили другие, есть 2 важных элемента информации:

  1. указатель this отсутствует при выполнении статического вызова функции и
  2. указатель 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;
}
0 голосов
/ 30 ноября 2009

Нет, это невозможно, поскольку статические члены связаны во время компиляции, а виртуальные члены связаны во время выполнения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...