2010-08-15 6 views
0

Edit: Обратите внимание, что моя конечная цель здесь не имеющий класс работает, просто узнать больше о шаблонах :-)Вложенные шаблоны и конструктор

Предположим, у вас есть шаблонный класс, который реализует вектор:

template <typename T> 
class Vector 
{ 
    public: 
     Vector(size_t dim) { 
      dimension = dim; 
      elements = new T[dim]; 
     } 
     /* Here more stuff, like operator[] etc... */ 
    private: 
     size_t dimension; 
     T * elements; 
} 

И предположим, что вы хотите построить с ним матрицу. Матрица просто вектор векторов, таким образом, он может быть сконструирован следующим образом:

template <typename T> 
class Matrix : public Vector<Vector<T> > 
{ 
    /*...*/ 
} 

И здесь приходит проблему: В конструкторе мне нужно предоставить строки и столбцы в качестве параметра для внутренних векторов. Это должно быть что-то вроде

template <typename T> 
Matrix<T>::Matrix (size_t ncols, size_t nrows) 
     : Vector<Vector<T> > /* Here I need to specify size for both 
           * internal and external vectors */ 
{ 
} 

Очевидно, что я не могу писать Vector<Vector<T>(nrows)>(ncols), но это то, что мне нужно!

Возможное решение будет в том числе размер внутри шаблона:

template <typename T, size_t N> 
class Vector 
{ 
    public: 
     Vector() { 
      elements = new T[N]; 
     } 
     /* Here more stuff, like operator[] etc... */ 
    private: 
     T * elements; 
} 

Поэтому я бы больше не нужны параметры конструктора, но это также заставляет меня писать неуклюжий код с шаблонами везде (по exmample, каждая функция, используя Vector должен быть объявлен как

template <typename T, size_t N> 
void foo (Vector<T,N> &vec) {...} 

у вас есть лучшие решения?

EDIT:

В качестве решения я черпал вдохновение из сообщений мистера Фуза и чумсада. Вот как я исправил проблему:

/* The RowAccess class is just an array wrapper which raises an exception 
* if you go out of bounds */ 
template <typename T> 
class RowAccess 
{ 
    public: 

     RowAccess (T * values, unsigned cols) : values(vals), cols(c) {} 

     T & operator[] (unsigned i) throw (MatrixError) { 
      if (i < cols) return values[i]; 
      else throw MatrixError("Column out of bound"); 
     } 

    private: 
     T * values; 
     unsigned cols; 
}; 

template <typename T> 
class Matrix 
{ 
    public: 
     Matrix (unsigned rows, unsigned cols) {...} 
     virtual ~Matrix() {...} 

     RowAccess<T> operator[] (unsigned row) { 
      if (row < rows) return RowAccess<T>(values + cols * row, cols); 
      else throw MatrixError("Row out of boundary"); 
     } 

    private: 
     unsigned rows; 
     unsigned cols; 
     T * values; 
}; 

Спасибо большое всем!

+0

Почему 'foo'« неуклюжий »? Даже без параметра N он все равно будет шаблонизирован. –

+0

И значение 'size_t N', и время выполнения' dim' для решения различных проблем. Если размер является постоянным и известен во время компиляции, вы должны * использовать метод 'size_t N', потому что он использует проверку типов. Возможное переполнение где-то затем диагностируется в compiletime. В этом случае также нет необходимости в 'new' вообще - просто поместите обычный массив внутри, например' boost :: array '. –

+0

@ jon hanson: да, но типы гораздо более общие, чем значения, поэтому определяющие 'foo (Vector &vec);' лучше, чем 'foo (Vector &vec);'. Для второго я предпочел бы определять foo как функцию шаблона. – Dacav

ответ

1

Это не то, что вы просили, но есть хорошая вероятность, что матрица будет лучше реализована как один линейный вектор, где вы предоставляете высокоуровневые методы доступа, которые выполняют индексирование (например, elmLoc = row * ncols + col). Таким образом, вам не нужно создавать и инициализировать вектор векторов. Вам также не нужно беспокоиться о том, чтобы случайно иметь некоторые внутренние векторы разного размера. Все плотные матричные реализации, которые я когда-либо использовал, использовали в качестве основной реализации один линейный вектор.

+0

Хорошее предложение, см. Обновление ответа. – Dacav

0

Это зависит от того, что вы ожидаете от своего класса Vector (и Matrix).

Либо вы хотите, чтобы размер определялся во время выполнения, и в этом случае я бы предложил добавить функцию resize(), которая позволит вам по размеру Vector в конструкторе по своему усмотрению.

template <typename T> 
class Vector 
{ 
    public: 
     Vector(size_t dim) { 
      dimension = dim; 
      elements = new T[dim]; 
     } 
     Vector() : dimension(0), elements(0) {} // you need default constructor 
     void resize(size_t dim) { // note: this could be implemented a lot better 
      T* new_elements=new T[dim]; 
      for(int i=0; i<dim && i<dimension; i++) 
      new_elements[i]=elements[i]; 
      delete [] elements; 
      elements=new_elements; dimension=dim; 
     } 
     /* Here more stuff, like operator[] etc... */ 
    private: 
     size_t dimension; 
     T * elements; 
} 

Вы бы затем изменить ваш Vectors в Matrix конструктора в цикле.

Если вы хотите, чтобы размер вашего вектора или матрицы определялся во время компиляции, лучше всего было бы использовать аргумент шаблона non-type, как вы предложили.

+0

Это может быть хорошим решением. Я также сильно разбираюсь в этом. Проблема заключается в том, что он позволяет пользователю создавать пустой вектор/матрицу, что делает библиотеку более слабой и подверженной ошибкам. – Dacav

2

Использование места размещения нового, как это (хоронили за uninitialized_fill вызова)

template <typename T> 
class Vector 
{ 
    public: 
     Vector(size_t dim, T const& c = T()) { 
      dimension = dim; 
      elements = 
       static_cast<T*>(operator new(sizeof(T) * dim)); 
      std::uninitialized_fill(elements, elements + dim, c); 
     } 
     /* Here more stuff, like operator[] etc... */ 
    private: 
     size_t dimension; 
     T * elements; 
}; 

Затем вы можете вызвать конструктор с Matrix::Vector(ncols, Vector<T>(nrows)) (вам не нужно повторять аргументы в пользу внешнего вектора, поскольку Vector относится до Vector< Vector<T> >, так как вы наследуете внешний вектор. Вам нужно обязательно вручную вызвать деструкторов, прежде чем делать operator delete(elements) в деструкторе.

Возможно, вы также захотите вставить вектор в качестве члена, который, вероятно, предпочтете, потому что я не уверен, что все операции внешнего вектора имеют смысл для матрицы. Затем инициализация выглядит как m(ncols, Vector<T>(nrows)).


Следует отметить, что std::vector также могут быть использованы для этой

template <typename T> 
class Vector 
{ 
    public: 
     Vector(size_t dim, T const& c = T()):elements(dim, c) 
     { } 
    private: 
     std::vector<T> elements; 
}; 

Это простой и безопасный способ сделать это, и вы получите автоматическое управление памятью.

+0

В основном вы используете 'operator new (sizeof (T) * dim) 'вместо' new T [dim] ', чтобы избежать вызова конструктора? – Dacav

+0

... Также является законным назвать супер-конструктор как« Matrix :: Vector (...) '? – Dacav

+0

@Dacav, вам нужно называть его таким образом. Если вы просто скажете' Vector (...) ', то имя не будет искать в базовом классе, потому что оно зависит от параметра шаблона. ompletely legal, чтобы назвать тип базового класса любым способом, который вы считаете нужным. Здесь вам не нужно 'typename', потому что, как и в случае с именами классов base-clause, подразумевается, что вы называете тип. –

3

В терминах OO я бы проголосовал за «имеет» связь между матрицей и вектором. Матрица имеет векторы, а не матрицу «является» вектором, что означает, что матрица не должна выводиться из вектора.

EDIT 1: Небольшая коррекция. «... это означает, что Матрица не должна выводиться« публично »из« Вектор ». Частное наследование может быть прекрасным.

+0

Это действительно хорошая идея (просто хорошо!), Но мне нужно переопределить два одинаковых метода (например, оператор [] один) – Dacav

+1

@Dacav: Наследование и состав не являются тем же самым, и наследование НЕ для кода re -использование. Вы открываете себя до неопределенного поведения, позволяя имплицировать неактивный вектор в вектор >, так как он не имеет виртуального деструктора.Когда вам нужно определить свой собственный оператор [] для класса Matrix, это однострочный перевод, чтобы переслать его базовому вектору. – Puppy

+0

@DeadMG: Ну, это мудрая точка зрения (+1 на ваш комментарий). Однако я пытаюсь научиться справляться с такой ситуацией (именно той, которую я описал в своем вопросе), которая происходит с наследованием. – Dacav

Смежные вопросы