C ++ std :: карта значений класса шаблона - PullRequest
13 голосов
/ 20 февраля 2009

Я пытаюсь объявить классы Row и Column, при этом Row имеет приватный std::map со значениями, указывающими на шаблонный Column. Как то так:

template <typename T>
class DataType {
  private:
    T type;
};
template <typename T>
class Field {
  private:
    T value;
    DataType<T> value;
};
class Row {
  private:
    std::map<unsigned long,Field*> column;
}; 

Ну, я полагаю, что в принципе класс Row не должен знать, какой тип Field (или Column) мы хотели бы использовать, то есть, будет ли это Field<int> в столбце 1 или Field<double> в столбце 2. Но я не уверен, каков правильный синтаксис для объявления Row::column, или если std::map ограничен в этом смысле, и я должен использовать что-то еще.

Я оцениваю ваши предложения и заранее благодарю за них.

Ответы [ 3 ]

24 голосов
/ 20 февраля 2009

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 :: Вариант делает всю работу за вас. Вы можете использовать посещение, чтобы вызвать функтор, используя правильную перегрузку. Взгляните на его руководство

1 голос
/ 20 февраля 2009
  1. Там вы получили ошибку: вы должны "оценить" элемент в поле (вероятно, это должен быть "тип").
  2. Пожалуйста, не держите сырые указатели в значении карты. Используйте boost :: shared_ptr .
  3. Кроме того, у вас должна быть веская причина для написания таких классов, где уже есть много кода для обработки БД / таблиц, который вы, вероятно, можете использовать. Поэтому, если это применимо, подумайте об использовании чего-либо существующего, а не о написании собственного кода обработки таблиц.

Теперь, чтобы ответить на ваш вопрос :), классы Field <> могут наследоваться от общего базового класса, который используется всеми типами данных. Таким образом, контейнер, такой как карта столбцов, может сохранять указатели (сделать так, чтобы shared указатели) на производные объекты, которые являются экземплярами класса шаблона.

0 голосов
/ 20 февраля 2009

A Row< int, float, int> действительно отличается от 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. Весь интеллект был впервые применен Локи (см. Современный дизайн C ++ , Андрей Александреску ), но Boost более современный и лучше поддерживается.

Обычно вы не будете перебирать поля - каждое поле имеет свой тип. Но из вас, вам действительно нужно FieldBase. Если вам нужен такой интерфейс, возможно, стоит также хранить поля внутри как boost::array<FieldBase, N> (то есть Row<int,float,int> имеет boost::array<FieldBase, 3>). Вы никогда не должны dynamic_cast, что FieldBase*, хотя. Это тест во время выполнения, и вы всегда знаете точные значения T каждого Field<T> во время компиляции.

...