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 ]

73 голосов
/ 30 ноября 2009

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

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

55 голосов
/ 30 ноября 2009

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

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

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

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

21 голосов
/ 30 ноября 2009

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

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

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

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

14 голосов
/ 11 мая 2010

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

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

Преимущества:

  1. меньше дублирования кода (но мы должен позвонить OVERRIDE_STATIC_FUNCTIONS в каждом конструктор)

Недостатки:

  1. OVERRIDE_STATIC_FUNCTIONS в каждом Конструктор
  2. память и производительность накладные расходы
  3. повышенной сложности

Открытые выпуски:

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

12 голосов
/ 15 февраля 2015

Хотя Альск уже дал довольно подробный ответ, я хотел бы добавить альтернативу, поскольку я думаю, что его расширенная реализация слишком сложна.

Мы начнем с абстрактного базового класса, который предоставляет интерфейс для всех типов объектов:

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 работает начиная с C ++ 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";

    // ...
};
11 голосов
/ 30 ноября 2009

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

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

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

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

6 голосов
/ 05 февраля 2013

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

3 голосов
/ 26 июня 2015

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

2 голосов
/ 30 ноября 2009

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

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>();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...