Имитация виртуального статического члена класса в С++?

Есть ли какой-нибудь виртуальный статический член в С++?

Например:

class BaseClass {
    public:
        BaseClass(const string& name) : _name(name) {}
        string GetName() const { return _name; }
        virtual void UseClass() = 0;
    private:
        const string _name;
};


class DerivedClass : public BaseClass {
    public:
        DerivedClass() : BaseClass("DerivedClass") {}
        virtual void UseClass() { /* do something */ }
};

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

class BaseClass {
    public:
        BaseClass() {}
        virtual string GetName() const = 0;
        virtual void UseClass() = 0;
};


class DerivedClass : public BaseClass {
    public:
        DerivedClass() {}
        virtual string GetName() const { return _name; }
        virtual void UseClass() { /* do something */ }
    private:
        static const string _name;
};

string DerivedClass::_name = "DerivedClass";

Это решение не удовлетворяет меня, потому что мне нужно переопределить имя участника и его getName() в каждом классе. В моем случае у меня есть несколько членов, которые следуют за действием _name и десятыми производными классами.

Любая идея?

Ответ 1

Вот одно из решений:

struct BaseData
{
  const string my_word;
  const int my_number;
};

class Base
{
public:
    Base(const BaseData* apBaseData)
    {
        mpBaseData = apBaseData;
    }
    const string getMyWord()
    {
        return mpBaseData->my_word;
    }
    int getMyNumber()
    {
        return mpBaseData->my_number;
    }
private:
    const BaseData* mpBaseData;
};

class Derived : public Base
{
public:
    Derived() : Base(&sBaseData)
    {
    }
private:
    static BaseData sBaseData;
}

BaseData Derived::BaseData = { "Foo", 42 };

Ответ 2

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

Если вы настаиваете на том, чтобы "общие" члены реализовывались как статические члены производного класса, вы могли бы автоматически генерировать код производных классов. XSLT - отличный инструмент для автоматического создания простых классов.

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

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

Пример того, что я описал выше:

class BaseClass {
    public:
        BaseClass(const Descriptor& desc) : _desc(desc) {}
        string GetName() const { return _desc.name; }
        int GetId() const { return _desc.Id; }
        X GetX() connst { return _desc.X; }
        virtual void UseClass() = 0;
    private:
        const Descriptor _desc;
};


class DerivedClass : public BaseClass {
    public:
        DerivedClass() : BaseClass(Descriptor("abc", 1,...)) {}
        virtual void UseClass() { /* do something */ }
};

class DerDerClass : public BaseClass {
    public:
        DerivedClass() : BaseClass("Wowzer", 843,...) {}
        virtual void UseClass() { /* do something */ }
};

Я хотел бы подробно остановиться на этом решении и, возможно, дать решение проблемы деинициализации:

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

Вы можете создать одноэлементный объект DescriptorMap, который будет содержать единственный экземпляр каждого дескриптора и использовать его при построении производных объектов следующим образом:

enum InstanceType {
    Yellow,
    Big,
    BananaHammoc
}

class DescriptorsMap{
    public:
        static Descriptor* GetDescriptor(InstanceType type) {
            if ( _instance.Get() == null) {
                _instance.reset(new DescriptorsMap());
            }
            return _instance.Get()-> _descriptors[type];
        }
    private:
        DescriptorsMap() {
            descriptors[Yellow] = new Descriptor("Yellow", 42, ...);
            descriptors[Big] = new Descriptor("InJapan", 17, ...)
            ...
        }

        ~DescriptorsMap() {
            /*Delete all the descriptors from the map*/
        }

        static autoptr<DescriptorsMap> _instance;
        map<InstanceType, Descriptor*> _descriptors;
}

Теперь мы можем сделать это:

class DerivedClass : public BaseClass {
    public:
        DerivedClass() : BaseClass(DescriptorsMap.GetDescriptor(InstanceType.BananaHammoc)) {}
        virtual void UseClass() { /* do something */ }
};

class DerDerClass : public BaseClass {
    public:
        DerivedClass() : BaseClass(DescriptorsMap.GetDescriptor(InstanceType.Yellow)) {}
        virtual void UseClass() { /* do something */ }
};

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

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

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

Ответ 3

@Hershi: проблема с этим подходом заключается в том, что каждый экземпляр каждого производного класса имеет копию данных, которая может быть дорогостоящей в некотором роде.

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


#include <iostream>
#include <string>
using namespace std;

struct DerivedData
{
  DerivedData(const string & word, const int number) :
    my_word(word), my_number(number) {}
  const string my_word;
  const int my_number;
};

class Base {
public:
  Base() : m_data(0) {}
  string getWord() const { return m_data->my_word; }
  int getNumber() const { return m_data->my_number; }
protected:
  DerivedData * m_data;
};


class Derived : public Base {
public:
  Derived() : Base() {
    if(Derived::s_data == 0) {
      Derived::s_data = new DerivedData("abc", 1);
    }
    m_data = s_data;
  }
private:
  static DerivedData * s_data;
};


DerivedData * Derived::s_data = 0; 

int main()
{
  Base * p_b = new Derived();
  cout getWord() << endl;
}

В отношении последующего вопроса об удалении статического объекта: единственным решением, которое приходит на ум, является использование умного указателя, что-то вроде Boost shared указатель.

Ответ 4

Я согласен с предложением Херши использовать шаблон в качестве "базового класса". Из того, что вы описываете, это больше похоже на использование шаблонов, а не на подклассы.

Вы можете создать шаблон следующим образом (не пытались его скомпилировать):


template <typename T>
class Object
{
public:

  Object( const T& newObject ) : yourObject(newObject) {} ;
  T GetObject() const { return yourObject } ;
  void SetObject( const T& newObject ) { yourObject = newObject } ;

protected:

  const T yourObject ;
} ;

class SomeClassOne
{
public:

  SomeClassOne( const std::vector& someData )
  {
    yourData.SetObject( someData ) ;
  }

private:

  Object<std::vector<int>> yourData ;
} ;

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

Если вы намерены использовать наследование, вам, возможно, придется прибегнуть к "радостям" использования указателя void * в вашем BaseClass и работе с литьем и т.д.

Однако, основываясь на вашем объяснении, кажется, что вам нужны шаблоны, а не наследование.

Ответ 5

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