Имитация виртуального статического члена класса в C ++? - PullRequest
11 голосов
/ 29 августа 2008

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

Например:

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

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

Есть идеи?

Ответы [ 5 ]

8 голосов
/ 30 августа 2008

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

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 голосов
/ 29 августа 2008

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

Если вы настаиваете на том, чтобы «общие» члены были реализованы как статические члены производного класса, вы можете автоматически генерировать код производных классов. 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.

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

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

1 голос
/ 29 августа 2008

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

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


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 * в базовом классе, работы с приведением типов и т. Д.

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

1 голос
/ 29 августа 2008

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

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


#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 указатель .

0 голосов
/ 22 мая 2009

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

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