Статические виртуальные элементы С++? - программирование

Статические виртуальные элементы С++?

Возможно ли, что в С++ есть функция-член, которая является как 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()), который может быть полезен для сопоставлений и жизненно важных для шаблонов.

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

Любые другие решения?

4b9b3361

Ответ 1

Нет, нет способа сделать это, так как что произойдет, когда вы позвонили Object::GetTypeInformation()? Он не может знать, какую версию производного класса нужно вызывать, поскольку нет связанного с ним объекта.

Вам нужно будет сделать его нестатической виртуальной функцией для правильной работы; если вы также хотите иметь возможность вызывать определенную версию производного класса практически без экземпляра объекта, вам также необходимо предоставить вторую старую не виртуальную версию.

Ответ 2

Многие говорят, что это невозможно, я бы сделал еще один шаг и сказал, что это не имеет смысла.

Статический член - это то, что не относится ни к одному экземпляру, только к классу.

Виртуальный член - это то, что не относится непосредственно к любому классу, только к экземпляру.

Таким образом, статический виртуальный член будет тем, что не относится ни к какому экземпляру или к любому классу.

Ответ 3

Я столкнулся с этой проблемой на днях: у меня было несколько классов, заполненных статическими методами, но я хотел использовать наследование и виртуальные методы и сократить повторение кода. Мое решение было:

Вместо использования статических методов используйте одноэлемент с виртуальными методами.

Другими словами, каждый класс должен содержать статический метод, который вы вызываете, чтобы получить указатель на один, общий экземпляр класса. Вы можете сделать истинные конструкторы частными или защищенными, чтобы внешний код не мог злоупотреблять им, создавая дополнительные экземпляры.

На практике использование singleton очень похоже на использование статических методов, за исключением того, что вы можете использовать наследование и виртуальные методы.

Ответ 4

Это возможно!

Но что именно возможно, пусть сужается. Люди часто хотят какой-то "статической виртуальной функции" из-за дублирования кода, необходимого для того, чтобы вызывать одну и ту же функцию через статический вызов "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:)

Итак, мы хотим предоставить только одно определение getTypeInformation() для производного класса, и очевидно, что это должно быть определение функции static, потому что невозможно вызвать "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(); }
};

Теперь мы можем сохранить тип объекта в базовом классе "Объект" с переменной "хранитель":

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 внутри каждого конструктора?

Ответ 5

Хотя 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;
}

Приложение (12 января 2019 г.):

Вместо использования функции GetClassNameStatic() вы также можете определить имя класса как статический член, даже "встроенный", который IIRC работает начиная с С++ 11 (не пугайтесь всех модификаторов :)):

class MyObject : public ObjectImpl<MyObject>
{
public:
    // Access this from the template class as 'ObjectType::s_ClassName' 
    static inline const char* const s_ClassName = "MyObject";

    // ...
};

Ответ 6

Это возможно. Сделайте две функции: статические и виртуальные

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
};

Ответ 7

Нет, это невозможно, потому что статические функции-члены не имеют указателя this. И статические члены (обе функции и переменные) на самом деле не являются членами класса. Они просто вызываются ClassName::member и придерживаются спецификаторов доступа к классу. Их хранение определяется где-то вне класса; хранилище не создается каждый раз, когда вы создаете объект класса. Указатели на члены класса являются особыми в семантике и синтаксисе. Указатель на статический член является нормальным указателем во всех отношениях.

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

Ответ 8

Ну, довольно поздний ответ, но это возможно, используя любопытно повторяющийся шаблон шаблона. Эта статья wikipedia содержит необходимую информацию, а также пример под статическим полиморфизмом - это то, о чем вас просят.

Ответ 9

Нет, статическая функция-член не может быть виртуальной. Поскольку виртуальная концепция разрешается во время выполнения с помощью vptr, а vptr является нестационарным членом класса. Таким образом, эта статическая функция-член не может обрабатывать vptr, поэтому статический член не может быть виртуальным.

Ответ 10

Я думаю, что вы пытаетесь сделать это через шаблоны. Я пытаюсь читать между строк здесь. То, что вы пытаетесь сделать, это вызвать метод из некоторого кода, где он вызывает производную версию, но вызывающий не указывает какой класс. Пример:

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>();
}

Ответ 11

Нет. У нас нет статических виртуальных функций в С++. Он полностью не используется.

Виртуальная функция Привязывается во время выполнения через вызов функции от объекта класса, где присутствует виртуальная функция.

Статические функции вовсе не являются частью класса. Тогда как эти две комбинации (статические виртуальные) будут работать... Вот почему у нас нет статических виртуальных функций в С++.

Ответ 12

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

Ответ 13

Во-первых, ответы правильны, что запрос OP является противоречием в терминах: виртуальные методы зависят от типа времени выполнения экземпляра; статические функции специально не зависят от экземпляра - только от типа. Тем не менее, имеет смысл статические функции возвращать что-то конкретное для типа. Например, у меня было семейство классов MouseTool для шаблона State, и я начал с того, что у каждого из них была статическая функция, возвращающая модификатор клавиатуры, который шел с ним; Я использовал эти статические функции в функции factory, которая сделала правильный экземпляр MouseTool. Эта функция проверила состояние мыши на MouseToolA:: keyboardModifier(), MouseToolB:: keyboardModifier() и т.д., А затем создала соответствующий экземпляр. Конечно, позже мне захотелось проверить правильность состояния, поэтому я хотел написать что-то вроде "if (keyboardModifier == dynamic_type (* state):: keyboardModifier())" (не настоящий синтаксис С++), и именно этот вопрос задает вопрос.

Итак, если вы захотите этого, вы можете захотеть создать свое решение. Тем не менее, я понимаю желание иметь статические методы, а затем динамически их называть динамическим типом экземпляра. Я думаю, что Шаблон посетителей может дать вам то, что вы хотите. Это дает вам то, что вы хотите. Это немного дополнительного кода, но он может быть полезен другим посетителям.

Смотрите: 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();
    }
};

Ответ 14

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

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 и добавьте некоторые другие вещи, и мы имеем:

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

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

Ответ 15

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

Смотрите https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

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

Ответ 16

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

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;
}

Ответ 17

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

  • нет указателя this при вызове статической функции и
  • указатель this указывает на структуру, в которой виртуальная таблица или thunk используются для поиска того, какой метод времени выполнения для вызова.

Статическая функция определяется во время компиляции.

Я показал этот пример кода в статических членах С++ в классе; он показывает, что вы можете вызвать статический метод с указанием нулевого указателя:

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;
}