Как создать массив шаблонных объектов класса?
Я давно не занимался программированием на C++, и я решил, что немного потороплюсь с этим в свободное время, поэтому я решил написать мне небольшую программу для баз данных просто для удовольствия, и у меня возникли проблемы с созданием массив шаблонных объектов класса.
У меня есть этот класс, который я хочу использовать для представления поля в записи базы данных.
template <class T, int fieldTypeId>
class Field
{
private:
T field;
int field_type;
public:
// ...
};
И я хочу использовать массив этого класса для представления записи в базе данных с использованием этого класса.
class Database_Record
{
private:
int id;
Field record[];
public:
Database_Record(int);
Database_Record(int, Field[]);
~Database_Record();
};
Где я застрял, это создание массива в Database_Record
class, так как это массив шаблонных объектов класса, где каждый элемент может быть другого типа, и я не уверен, как мне нужно объявить массив из-за этого. Возможно ли то, что я пытаюсь сделать, или я поступаю неправильно? Любая помощь будет принята с благодарностью.
8 ответов
Field<T1>
а также Field<T2>
два совершенно разных типа. Чтобы рассматривать их в векторе, нужно где-то обобщать. Вы можете написать AbstractField
а также
struct AbstractField{
virtual ~AbstractField() = 0;
};
template<class T,int fieldTypeId>
class Field: public AbstractField{
private:
T field;
public:
const static int field_type;
public:
virtual ~Field(){}
};
class Database_Record{
std::vector<AbstractField*> record;
public:
~Database_Record(){
//delete all AbstractFields in vector
}
};
а затем держать vector
из AbstractField
, также использовать vector
вместо []
, использование AbstractField*
вместо AbstractField
и напиши хотя бы один чистый виртуал в AbstractField
,
вы можете сделать деструктором AbstractField
чисто виртуальный. и не забудьте удалить все AbstractField
s. в ~Database_Record()
Как уже было сказано, шаблоны C++ не работают так.
В то же время использование наследования и векторов указателей не подходит для реализации записей БД из-за ограничений производительности.
Сделайте шаг назад и посмотрите на проблему более абстрактно. Как я понял из вашего кода, цель состоит в том, чтобы упаковать произвольное количество полей разных типов в непрерывный блок памяти. Схематично:
struct DBRecord {
Type1 f1;
Type2 f2;
Type3 f3;
Type4 f4;
// etc...
}
Вы можете достичь этого с помощью немного уродливой, но практичной конструкции, состоящей из объявления абстрактного шаблона и нескольких специализаций.
Декларация будет выглядеть так:
template <
typename T1,
typename T2 = void,
typename T3 = void,
typename T4 = void,
typename T5 = void,
typename T6 = void,
typename T7 = void,
typename T8 = void,
typename T9 = void,
typename T10 = void
> struct DBRecord;
Это ограничивает максимальное количество полей до некоторого определенного числа, очевидно. Если вам нужно действительно произвольное количество полей, вам нужно перейти к парадигме, ориентированной на столбцы.
Затем частичные специализации должны объявлять анатомию структур для каждого числа аргументов от 1 до 10:
template <
typename T1
> struct DBRecord <T1, void, void, void, void, void, void, void, void, void>
{
int id;
T1 f1;
DBRecord(int ID, T1 F1) {/*...*/};
};
template <
typename T1,
typename T2
> struct DBRecord <T1, T2, void, void, void, void, void, void, void, void>
{
int id;
T1 f1;
T2 f2;
DBRecord(int ID, T1 F1, T2 F2) {/*...*/};
};
// etc...
Теперь вы можете размещать таблицы как массивы записей определенных типов в одном new[]
позвони если хочешь. И, как правило, вас не волнует уничтожение каждого поля, поскольку вы освобождаете память всей структуры.
Макросы могут помочь сделать объявление таких специализаций несколько более компактным.
Чтобы создать массив разных типов, вам нужен базовый класс для объектов, а массив будет массивом указателей на этот базовый класс. Так, например,
class Field
{
public:
virtual ~Field() {}
virtual std::string toString() const = 0;
// and possibly other interface functions...
};
template <class T> FieldImpl : public Field
{
public:
virtual std::string toString() const
{
std::stringstream ss;
ss << val;
return ss.str();
}
// implementation of possibly other interface functions
private:
T val;
}
будут типы, которые вам нужны. Массив будет что-то вроде
std::vector<std::unique_ptr<Field>> my_array;
Затем вы можете делать вещи с вашим массивом, используя функции интерфейса, например,
my_array[i]->toString();
Вы идете не в ту сторону.
Шаблоны используются для создания различных типов: std::vector<int>
а также std::vector<float>
различаются во многом так же (и так), как int
а также float
являются.
Ваш синтаксис также неверен; чтобы создать динамический массив, вы бы поместили следующий член в вашем Database_Record
:
std::vector<Field> record; // if this was possible; however, it's not
Чтобы поместить несколько объектов различного типа в один массив, они должны иметь общий базовый класс.
Рассмотрим каждый экземпляр с другим аргументом шаблона как отдельный класс. Вам либо нужно хранить определенный класс (т.е. Field<int, 17>
) или вам нужно Field
иметь базовый класс без шаблонов, который вы можете хранить в списке.
Вы можете сделать что-то вроде этого -
template <class T, int fieldTypeId>
class Field
{
private:
T field;
int field_Type;
};
template <class T, int fieldTypeId>
class Database_record
{
private:
int id;
std::vector<Field<T, fieldTypeId> > record_;
};
Сделано 2 примера классов, используемых для быстрых отчетов об отладке, вдохновленных переопределением ToString() C#:
class UnknownType_t {
public:
virtual operator long&() { throw "Unsupported"; };
virtual operator const std::string() { throw "Unsupported"; };
virtual void Set(char*, long) = 0;
};
class Number : public UnknownType_t {
public:
Number(long _n) { n = _n; };
virtual operator long&() { return n; };
virtual void Set(char* buf, long size) {
n = 0;
memcpy(&n, buf, size);
}
long n;
};
class String : public UnknownType_t {
public:
String(const char *_s) : s(_s) {};
virtual operator const std::string() { return s; };
virtual void Set(char* buf, long size) {
s = std::string(reinterpret_cast<char*>(buf), size);
}
std::string s;
};
Вы можете проверить тип, пробуя dynamic_cast, в результате общий массив UnknownType_t будет выглядеть как {n=123} или {s="ABC"}.
База не является чисто виртуальной по намерению - требуемые перекрестные геттеры не имели бы смысла ...
Вы делаете шаблоны неправильно. Создание шаблонов классов с разными типами приведет к двум разным типам снова с возможно разными размерами, что делает невозможным их сохранение в массиве.
Если вы хотите обрабатывать разные типы одинаково, используйте наследование. И когда вы используете наследование, не используйте простые массивы, но vector
или жеstd::array
,
В вашем коде также есть несколько странных вещей: зачем хранитьfieldTypeId
когда это известно статически? Я думаю, это связано с типом T
вы используете в качестве параметра шаблона. Вывести механизм из частичной специализации:
template<typename T>
struct fieldTypeId;
template<>
struct fieldTypeId<int> {
const static int value = 0;
};
// etc....
Если я полностью неправ, и вы действительно знаете, что делаете: используйте стирание типов через некоторые any
тип (например, Boost.Any).