С++ std:: map значений шаблона
Я пытаюсь объявить классы Row
и Column
, причем Row
имеет закрытый std::map
со значениями, указывающими на шаблонный Column
. Примерно так:
template <typename T> class DataType {
private:
T type;
};
template <typename T> class Field {
private:
T value;
DataType<T> type;
};
class Row {
private:
std::map<unsigned long,Field*> column;
};
Ну, я полагаю, что в принципе класс Row
не должен знать, какой тип Field
(или Column
) мы хотели бы использовать, т.е. является ли он Field<int>
в столбце 1 или Field<double>
в столбце 2. Но я не уверен, какой правильный синтаксис для объявления Row::column
, или если std::map
ограничен в этом смысле, и я должен использовать что-то еще.
Я оцениваю ваши предложения и заранее благодарю за них.
Ответы
Ответ 1
Field
один не тип, а шаблон, который может генерировать семейство типов, таких как Field<int>
и Field<double>
. Все эти поля не связаны друг с другом, так что они каким-то образом выведены из другого или такого. Поэтому вам нужно установить некоторую взаимосвязь между всеми этими сгенерированными типами. Один из способов - использовать общий базовый класс без шаблона:
class FieldBase { };
template <typename T>
class Field : public FieldBase {
private:
T value;
DataType<T> type;
};
class Row {
private:
std::map<unsigned long,FieldBase*> column;
};
И рассмотрите возможность использования интеллектуального указателя вместо этого необработанного указателя в коде. В любом случае проблема заключается в том, что информация о типе теряется - указывает ли вы на Field<double>
или на Field<int>
больше не известна и может быть обнаружена только путем хранения определенного типа флага в базе, которая является заданный шаблоном производного класса - или путем запроса RTTI с использованием
dynamic_cast<Field<int>*>(field) != 0
Но это уродливо. Тем более, что то, что вы хотите, есть ценностная семантика. Я хочу, чтобы вы могли копировать свою строку и копировать все поля в ней. И вы захотите получить двойное число, когда будет сохранен двойной - без использования RTTI, чтобы взломать ваш путь к производному типу.
Один из способов сделать это - использовать дискриминационный союз. Это в основном объединение для некоторых произвольных типов и, кроме того, флаг типа, в котором хранится то, какое значение в настоящее время хранится в этом поле (например, есть ли double, int,...). Например:
template <typename T>
class Field {
private:
T value;
DataType<T> type;
};
class Row {
private:
std::map<unsigned long,
boost::variant< Field<int>, Field<double> > >
column;
};
boost:: variant делает всю работу за вас. Вы можете использовать посещение, чтобы заставить его вызвать функтор, используя правильную перегрузку. Посмотрите на руководство
Ответ 2
- У вас есть ошибка: вы должны "присваивать" член в поле (вероятно, должно быть "type" ).
- Пожалуйста, не оставляйте исходные указатели в значении карты. Используйте boost:: shared_ptr.
- Кроме того, у вас должна быть веская причина для написания таких классов, где есть много кода обработки DB/таблицы, которые уже можно использовать. Поэтому, если это применимо, подумайте о том, чтобы использовать что-то существующее и не писать свой собственный код обработки таблицы.
Теперь, чтобы ответить на ваш вопрос:), классы Field < > могут наследоваться от общего базового класса, который используется всеми типами данных. Таким образом, контейнер, такой как ваша карта столбца, может содержать указатели (сделать указатели разделяемые) производными объектами, которые заданы для класса шаблона.
Ответ 3
A Row< int, float, int>
действительно отличается от a Row<int, std::string>
.
Ясно, что Row<int,float,int>.field<0>
должен быть Field<int>
, а Row<int,float,int>.field<1>
должен быть Field<float>
. И Row<int,float,int>.field<3>
- ошибка компилятора.
Самый простой способ сделать это - использовать Boost. Весьма интеллект был основан Локи (см. "Современный дизайн С++" Андрея Александреску), но Boost является более современным и лучше поддерживается.
Обычно вы не должны перебирать поля - каждое поле имеет свой собственный тип. Но вы действительно нуждаетесь в FieldBase
. Если вам нужен такой интерфейс, вероятно, стоит также сохранить поля внутри, как boost::array<FieldBase, N>
(т.е. Row<int,float,int>
имеет boost::array<FieldBase, 3>
). Однако вам не нужно dynamic_cast
, что FieldBase*
. Это тест времени выполнения, и вы всегда знаете точный T
для каждого Field<T>
во время компиляции.