Имитация виртуального статического члена класса в C ++?
-
09-06-2019 - |
Вопрос
Есть ли какой-нибудь виртуальный статический член в 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 и десятыми долями производных классов.
Есть идеи?
Решение
Вот одно из решений:
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 };
Другие советы
Кажется, что ответ в вопросе - предложенный вами метод, кажется, является правильным направлением, за исключением того, что если у вас есть большое количество этих общих членов, вы можете собрать их в структуру или класс и мимо этого в качестве аргумента для конструктора базового класса.
Если вы настаиваете на том, чтобы " поделился " члены, реализованные как статические члены производного класса, вы можете автоматически генерировать код производных классов. XSLT - отличный инструмент для автоматической генерации простых классов.
В общем, пример не показывает необходимость "виртуальной статики" члены, потому что для подобных целей вам на самом деле не нужно наследование - вместо этого вы должны использовать базовый класс и принимать его в конструкторе с соответствующими значениями - возможно, создавая отдельный экземпляр аргументов для каждого " подтипа " и передача указателя на него, чтобы избежать дублирования общих данных. Другой аналогичный подход заключается в использовании шаблонов и передаче в качестве аргумента шаблона класса, который предоставляет все соответствующие значения (это обычно называется шаблоном " Policy ").
В заключение - для целей исходного примера такая «виртуальная статическая» не нужна. члены. Если вы все еще думаете, что они необходимы для написанного вами кода, попробуйте разработать и добавить больше контекста.
Пример того, что я описал выше:
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.
Итак, теперь у нас есть один экземпляр каждого дескриптора, который также удаляется в конце выполнения.
Обратите внимание, что если единственной целью производного класса является предоставление соответствующего " дескриптора " данные (т. е. в отличие от реализации виртуальных функций), тогда вы должны покончить с тем, чтобы сделать базовый класс неабстрактным, и просто каждый раз создавать экземпляр с соответствующим дескриптором.
Я согласен с предложением Херши использовать шаблон в качестве "базового класса". Из того, что вы описываете, это больше похоже на использование шаблонов, чем на создание подклассов.
Вы можете создать шаблон следующим образом (не пытались его скомпилировать):
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 и работы с приведением типов и т. д.
Однако, исходя из вашего объяснения, кажется, что вам нужны шаблоны, а не наследование.
@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;
}
Относительно последующего вопроса об удалении статического объекта: единственное решение, которое приходит на ум, - это использовать умный указатель, например, Увеличить общий указатель .
Звучит так, как будто вы пытаетесь избежать дублирования кода в листовых классах, так почему бы просто не получить промежуточный базовый класс из базового класса. этот промежуточный класс может содержать статические данные, и все ваши конечные классы являются производными от промежуточного базового класса. Это предполагает, что желательна одна статическая часть данных, хранящаяся во всех производных классах, как это видно из вашего примера.