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

141

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

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

Какие-нибудь другие решения?

cvb
источник
12
Просто побочный комментарий: статические методы не выполняются ни в одном экземпляре, что означает, что они не имеют неявного указателя this. При этом constв сигнатуре метода помечается неявный thisуказатель как константа, и он не может применяться к статическим методам, поскольку у них отсутствует неявный параметр.
Дэвид Родригес - дрибес, 01
2
@cvb: Я бы серьезно пересмотрел вопрос о замене вашего примера на код, не требующий отражения. В нынешней ситуации вы как бы объединяете две отдельные (хотя и связанные) проблемы. Да, и я знаю, что прошло 5 с половиной лет с тех пор, как вы об этом спросили.
einpoklum
Одна из неявно необходимых здесь функций - это проверка компилятором того, что каждый объект в иерархии реализует определенный интерфейс (где один или несколько методов являются статическими). По сути, чистая виртуальная проверка статического метода имеет большой смысл, поскольку, если вы забудете добавить статический метод, компилятор должен вывести ошибку. virtual здесь не ключевое слово, это более абстрактное слово, которое в C ++ является своего рода синонимом, за исключением этого конкретного случая. К сожалению, в настоящее время это невозможно сделать на C ++.
xryl669 02

Ответы:

75

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

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

Адам Розенфилд
источник
8
Если вы думаете о статическом классе (или статических членах классов) как о синглтоне, все становится очевидным - в вашем случае следует вызывать просто Object :: GetTypeInformation - так же, как вызов обычного виртуального метода в экземпляре базового класса . (Конечно, если C ++ поддерживает виртуальные статические методы)
Spook
13
Это совершенно правдоподобный аргумент. Если вы используете класс вместо объекта, он, естественно, будет использовать версию из этого класса вместо выполнения виртуальной отправки. Ничего нового.
Дедупликатор
53

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

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

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

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

Расмус Кай
источник
44
Это имеет большое значение для языков, где классы являются первоклассными значениями - например, в Delphi это есть, а также есть «статические виртуальные» методы.
Павел Минаев
4
В яблочко. «Виртуальная функция» - это (по определению) функция, которая динамически связана , то есть она выбирается во время выполнения в зависимости от динамического типа данного объекта. Следовательно, нет объекта = нет виртуального вызова.
Кос,
8
Я также считаю, что статические виртуалы имеют значение. Можно было бы определить классы интерфейса и включить статические методы, которые должны быть реализованы в производном классе.
bkausbk
36
Это не так важно для static virtualметода, но static чистый virtual метод очень важен для интерфейса.
Брет Кунс,
5
Это имеет смысл, если вы намереваетесь использовать компилятор, чтобы убедиться, что вы реализовали статический метод во всех дочерних классах. @BretKuhns прав.
xryl669
23

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

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

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

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

Нейт СК
источник
Это будет стоить мне производительности - если только компилятор не может быть уверен, что: 1. Это на самом деле синглтон и 2. Ничего от него не наследуется, я не думаю, что он может оптимизировать все накладные расходы.
einpoklum
Если производительность такого рода вещей вас беспокоит, то C #, вероятно, вам не подходит.
Nate CK
3
Ах, хорошее замечание. Очевидно, я давно не думал об этом с тех пор, как написал это в 2009 году. Позвольте мне сказать это иначе: если вас беспокоит такая производительность, то, возможно, вам следует полностью отказаться от использования наследования. Плакат специально просил о виртуальных методах, поэтому странно, что вы пришли сюда, чтобы пожаловаться на накладные расходы виртуальных методов.
Nate CK
15

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

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

Итак, мы хотим предоставить только одно определение 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 внутри каждого конструктора?

Альск
источник
+1 за усилия, хотя я не уверен, что это более элегантно, чем просто делегирование функций синглтону с помощью виртуальных методов.
einpoklum
1
@einpoklum, я могу придумать ситуацию, когда это может быть предпочтительнее. Предположим, у нас есть много клиентского кода, который уже вызывает статические методы. Переход от статических методов к одноэлементным с виртуальными методами потребует изменений в клиентском коде, в то время как представленное выше решение является неинвазивным.
Alsk
Ключевое слово virtual не требуется для Foo :: getTypeInformation и TypeKeeperImpl :: getTypeInformation.
bartolo-otrit
12

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

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

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

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

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
};
Алексей Малистов
источник
4
Кроме того, статические методы не могут быть константами. Это просто не имеет смысла, какой экземпляр они не собираются мутировать?
Дэвид Родригес - дрибес, 01
1
В основном это просто дублирование кода. Идея состоит в том, чтобы подклассы имели только статический константный член, а не код, обращающийся к нему.
einpoklum
8

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

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


источник
1
this Указатель нужен только нестатическим функциям . статические функции не относятся к экземпляру и не нуждаются в этом. Итак - это не причина, по которой виртуальные статические члены невозможны.
einpoklum 03
7

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

тропикана
источник
3

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

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>();
}
zumalifeguard
источник
1
Если вы сделаете отступ в 4 пробела в коде, то сможете автоматически отформатировать его. В качестве альтернативы я считаю, что вы можете использовать обратную галочку для достижения той же цели в строке.
chollida
1
Это очевидное, что я упустил. Спасибо. Тем не менее, лобковые члены странно.
allesblinkt
M () не является статической функцией. как это называется T :: M ()?
DDukDDak99 01
3

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

Прабхат Кумар
источник
2
Только виртуальные методы для конкретных экземпляров требуют экземпляров vtable. У вас может быть статическая vtable - одна для каждого класса. И если вы хотите, чтобы экземпляры знали об этом, просто укажите из vtable экземпляра также на vtable статики класса.
einpoklum 03
3

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

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

Рассмотрим, как работают обычные виртуальные функции. Удалите statics и добавьте еще кое-что, и мы получим:

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

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

Пол Дж. Уильямс
источник
0

Во-первых, ответы верны: то, что запрашивает OP, противоречит терминам: виртуальные методы зависят от типа времени выполнения экземпляра; статические функции конкретно не зависят от экземпляра - только от типа. Тем не менее, имеет смысл, чтобы статические функции возвращали что-то конкретное для типа. Например, у меня было семейство классов MouseTool для паттерна State, и я начал использовать в каждом из них статическую функцию, возвращающую соответствующий модификатор клавиатуры; Я использовал эти статические функции в фабричной функции, которая создала правильный экземпляр MouseTool. Эта функция проверила состояние мыши по MouseToolA :: keyboardModifier (), MouseToolB :: 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();
    }
};
Бен
источник
виртуальные статические методы, если бы они существовали, не зависели бы ни от чего в экземпляре, но экземпляру нужно было бы знать свой тип, чтобы вызывать их. Это может быть реализовано компилятором (например, с помощью некоторой отдельной структуры данных для каждого класса с указателями на виртуальные статические методы и члены). Это определенно не противоречит терминам.
einpoklum
Является ли это противоречием в терминах или нет - вопрос семантики. Можно представить, что C ++ позволяет вызывать статику из экземпляра (например, Foo foo; ... foo::bar();вместо Foo::bar();). Это мало чем отличается, decltype(foo)::bar();но это снова будет статически привязано. Подход посетителя кажется разумным способом добиться такого поведения, не делая статический метод просто виртуальным константным методом.
Бен
0

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

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

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


источник
-1

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

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;
}
Jiez
источник
Да, я знаю, 4 года. Объяснение -score для тех, кто не хочет читать код в таких подробностях. Base::mSelfссылается на САМЫЙ НЕДАВНО созданный экземпляр любого производного класса, даже если этот экземпляр был уничтожен . так class D1 : public Base ...; class D2 : public Base ...; ...; D1* pd1 = new D1(); D2* pd2 = new D2(); pd1->MyStaticFun(); /* calls D2::MyVirtualFun() */ delete pd2; pd1->MyStaticFun(); /* calls via deleted pd2 */что это НЕ то, что нужно.
Джесси Чизхолм
-3

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

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

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

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

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;
}
холлида
источник
6
Технически это неопределенное поведение. Вы не можете уважать нулевой указатель ни по какой причине. Единственное, что вы можете делать с нулевым указателем, - это а) назначить ему другой указатель и б) сравнить его с другим указателем.
KeithB
1
Кроме того, вы можете сравнивать его только на равенство (или неравенство_ с другим указателем, а не на порядок. Т.е. p < null, и p >= nullт.д., все также не определены.
Павел Минаев
1
@KeithB - ​​для полноты вы также можете безопасно вызывать удаление для нулевого указателя.
Стив Роу