Мне нужно хранить список различных свойств объекта. Свойство состоит из имени и данных, которые могут иметь любой тип данных.
Я знаю, что могу создать класс «Свойство» и расширить его различными классами PropertySubClasses, которые отличаются только типом данных, который они хранят, но он не чувствует
class Property
{
Property(std::string name);
virtual ~Property();
std::string m_name;
};
class PropertyBoolean : Property
{
PropertyBoolean(std::string name, bool data);
bool m_data;
};
class PropertyFloat : Property
{
PropertyFloat(std::string name, float data);
float m_data;
};
class PropertyVector : Property
{
PropertyVector(std::string name, std::vector<float> data);
std::vector<float> m_data;
};
Теперь я могу хранить все виды свойств в
std::vector<Property*>
и, чтобы получить данные, я могу привести объект к подклассу. Или я могу сделать чисто виртуальную функцию, чтобы что-то делать с данными внутри функции без необходимости приведения.
В любом случае, это неправильно для создания этих различных подклассов, которые отличаются только типом данных, которые они хранят. , Есть ли другой удобный способ добиться подобного поведения?
У меня нет доступа к Boost.
C ++ - это многопарадигмальный язык. Он наиболее ярко проявляется и наиболее действенен там, где смешиваются парадигмы.
class Property
{
public:
Property(const std::string& name) //note: we don't lightly copy strings in C++
: m_name(name) {}
virtual ~Property() {}
private:
std::string m_name;
};
template< typename T >
class TypedProperty : public Property
{
public:
TypedProperty (const std::string& name, const T& data)
: Property(name), m_data(data);
private:
T m_data;
};
typedef std::vector< std::shared_ptr<Property> > property_list_type;
Изменить: Почему используется std :: shared_ptr
вместо Свойство *
?
Рассмотрим этот код:
void f()
{
std::vector<Property*> my_property_list;
for(unsigned int u=0; u<10; ++u)
my_property_list.push_back(new Property(u));
use_property_list(my_property_list);
for(std::vector<Property*>::iterator it=my_property_list.begin();
it!=my_property_list.end(); ++it)
delete *it;
}
Этот цикл for
пытается очистить, удаляя все свойства в векторе, непосредственно перед тем, как он выйдет за пределы области видимости и заберет с собой все указатели.
Хотя для новичка это может показаться нормальным, но если вы - лишь слегка опытный разработчик C ++, этот код должен вызвать тревогу, как только вы на него посмотрите.
Проблема в том, что вызов use_property_list ()
может вызвать исключение. В этом случае функция f ()
сразу же будет оставлена. Для правильной очистки будут вызваны деструкторы для всех автоматических объектов, созданных в f ()
. То есть my_property_list
будет правильно уничтожен. Затем деструктор std :: vector
аккуратно очистит содержащиеся в нем данные. Однако он содержит указателей , и как std :: vector
узнать, являются ли эти указатели последними, ссылающимися на их объекты?
Поскольку он не знает, он не будет удалять объекты, он уничтожит указатели только тогда, когда уничтожит его содержимое, оставив вам объекты в куче, на которые у вас больше нет указателей. Это то, что называется «утечкой».
Чтобы этого избежать, вам нужно будет перехватить все исключения, очистить свойства и повторно выбросить исключение. Но затем, через десять лет, кто-то должен добавить новую функцию в приложение 10MLoC, до которого оно выросло, и, торопясь, добавить код, который преждевременно покидает эту функцию, когда выполняется какое-либо условие. Код протестирован, работает и не дает сбоев - только сервер, частью которого он является, теперь пропускает несколько байтов в час, что приводит к сбою из-за нехватки памяти примерно раз в неделю. Обнаружение этого делает многие часы прекрасной отладкой.
Итог: никогда не управляйте ресурсами вручную, всегда оборачивайте их в объекты класса, предназначенные для обработки ровно одного экземпляра такого ресурса. Для динамически выделяемых объектов эти дескрипторы называются «интеллектуальным указателем» , а наиболее часто используемый - shared_ptr
.
Я предлагаю boost :: variant
или boost :: any
. [ Связанный вопрос ]
Я вижу, что есть много попыток чтобы решить вашу проблему к настоящему моменту, но у меня такое чувство, что вы смотрите не в ту сторону - почему вы на самом деле вообще хотите это сделать? Есть ли в базовом классе какие-то интересные функции, которые вы не указали?
Тот факт, что вам придется включить идентификатор типа свойства, чтобы делать то, что вы хотите с конкретным экземпляром, является запахом кода, особенно , когда подклассы не имеют абсолютно ничего общего через базовый класс, кроме имени (которое в данном случае является идентификатором типа).
Вы, вероятно, можете сделать это с помощью библиотеки Boost, или вы могли бы создать класс с кодом типа и указателем void
на данные, но это означало бы отказ от некоторой типовой безопасности C ++ . Другими словами, если у вас есть свойство «foo», значение которого является целым числом, и вы вместо этого присваиваете ему строковое значение, компилятор не найдет для вас ошибку.
Я бы порекомендовал пересмотреть ваш дизайн и переоценить, действительно ли вам нужна такая гибкость. Вам действительно нужно иметь возможность обрабатывать свойства любого типа? Если вы можете сузить его до нескольких типов, вы сможете найти решение, используя наследование или шаблоны, без необходимости «бороться с языком».
Другое возможное решение - написать промежуточный класс, управляющий указателями на классы Property
:
class Bla {
private:
Property* mp
public:
explicit Bla(Property* p) : mp(p) { }
~Bla() { delete p; }
// The standard copy constructor
// and assignment operator
// aren't sufficient in this case:
// They would only copy the
// pointer mp (shallow copy)
Bla(const Bla* b) : mp(b.mp->clone()) { }
Bla& operator = (Bla b) { // copy'n'swap trick
swap(b);
return *this;
}
void swap(Bla& b) {
using std::swap; // #include <algorithm>
swap(mp, b.mp);
}
Property* operator -> () const {
return mp;
}
Property& operator * () const {
return *mp;
}
};
Вы должны добавить виртуальный метод clone
в ваши классы возвращают указатель на только что созданную копию самого себя:
class StringProperty : public Property {
// ...
public:
// ...
virtual Property* clone() { return new StringProperty(*this); }
// ...
};
Затем вы сможете сделать это:
std::vector<Bla> v;
v.push_back(Bla(new StringProperty("Name", "Jon Doe")));
// ...
std::vector<Bla>::const_iterator i = v.begin();
(*i)->some_virtual_method();
Выход из области видимости v
означает, что все Bla
s будет уничтожен, автоматически освободив указатели, которые они содержат. Из-за перегруженного оператора разыменования и косвенного обращения класс Bla
ведет себя как обычный указатель. В последней строке * i
возвращает ссылку на объект Bla
, а использование ->
означает то же самое, как если бы это был указатель на свойство
объект.
Возможный недостаток этого подхода состоит в том, что вы всегда получаете операцию кучи ( new
и delete
), если промежуточные объекты необходимо скопировать. Это происходит, например, если вы превышаете емкость вектора и все промежуточные объекты должны быть скопированы в новый участок памяти.
В новом стандарте (т. Е.c ++ 0x) вы сможете использовать шаблон unique_ptr
: его
auto_ptr
, который нельзя использовать в стандартных контейнерах), Способ более низкого уровня — использовать объединение
class Property
union {
int int_data;
bool bool_data;
std::cstring* string_data;
};
enum { INT_PROP, BOOL_PROP, STRING_PROP } data_type;
// ... more smarts ...
};
Не знаю, почему ваше другое решение кажется вам неправильным, поэтому я не знаю, будет ли этот способ лучше для вас.
РЕДАКТИРОВАТЬ: Еще немного кода, чтобы привести пример использования.
Property car = collection_of_properties.head();
if (car.data_type == Property::INT_PROP) {
printf("The integer property is %d\n", car.int_data);
} // etc.
Вероятно, я бы поместил такую логику в метод класса, где это возможно. У вас также должны быть члены, такие как этот конструктор, для синхронизации данных и поля типа:
Property::Property(bool value) {
bool_data = value;
data_type = BOOL_PROP;
}
Напишите шаблонный класс Property
, производный от Property
с элементом данных типа T