Типы базовых / производных шаблонов классов - PullRequest
0 голосов
/ 08 сентября 2018

У меня есть шаблонный класс, построенный на std::vector. Похоже ...

template <class T>
class Vector {
public:
    std::vector<T> vec;

    // Constructors...
    // Operator overloads...
    // Methods...
    // etc.
};

Я хочу создать производный шаблонный класс ComplexVector, для которого тип данных будет std::complex<T>, и, конечно, я хочу повторно использовать методы, перегрузки операторов и т. Д., Которые я создал в классе Vector.

Проблема в том, что, кажется, есть два способа сделать это.

1) Принудительно объявить std::complex<T> типом шаблона.

ComplexVector< std::complex<float> > myVec;

2) Объявите ComplexVector только со скалярным типом, но затем передайте тип std::complex конструктору Vector.

ComplexVector<float> myVec;
// Constructor
ComplexVector<T>::ComplexVector() : Vector< std::complex<T> >

Вариант 1 гораздо проще с точки зрения разработки программного обеспечения, но уродлив для пользователя, тогда как вариант 2 гораздо приятнее для пользователя. Я хотел бы сделать вариант № 2, но я беспокоюсь о том, как это сработает.

Если я передам std::vector<T> конструктору базового класса, это изменит только то, что делает конструктор, или весь шаблон перейдет с типа T на std::complex< T >?

Если все не преобразуется в std::complex<T>, значит ли это, что мне придется перегружать все методы, которые есть в Vector?

Ответы [ 4 ]

0 голосов
/ 13 сентября 2018

В комментариях к исходному q. @JimClay разъясняет вариант использования: он хочет расширить сложный вектор с помощью таких методов, как fft (быстрое преобразование Фурье).

Конечно, можно извлечь из vector<T> создать новый класс, скажем, ComplexVector<T> и добавить метод, подобный fft, но я не рекомендую его. Подробные причины ниже. Лучшая альтернатива - добавить отдельную функцию, такую ​​как

template<typename T>
vector<complex<T>> fft(const vector<complex<T>>&);

(это с using namespace std для удобства чтения).

Вот причин не использовать производный шаблон класса :

  • Предположим, вы хотите использовать библиотечную функцию, которая возвращает vector<complex<T>> v. Если вы бросили свой собственный ComplexVector<T>, вам нужно взять v и преобразовать его в ComplexVector<T> перед вызовом fft. Это может быть сделано эффективно, но это требует от пользователей быть осторожным с построением ходов.
  • Становится хуже. Предположим, что вы и я в одной ситуации, и оба бросают свои собственные классы, MyComplexVector<T> и YourComplexVector<T>. Тогда пользователи, которые хотят использовать наши библиотеки вместе, должны будут взять MyComplexVector<T>, преобразовать его в vector<T>, а затем преобразовать в YourComplexVector<T> перед вызовом моей библиотеки. [Хотя в большинстве случаев первое преобразование происходит неявно.]
  • Пользователям нужно будет поработать, чтобы ознакомиться с семантикой ComplexVector<T>, тогда как каждый программист C ++ знает, как ведет себя vector<T>.

Я достаточно взрослый, чтобы использовать C ++ до STL, и совместное использование библиотек не было забавным! Действительно, совместимость библиотек была одной из основных причин введения стандарта string, vector и т. Д.

Обратите внимание, что если вы хотите реализовать эффективный fft на месте, вам понадобится справочная версия rvalue:

template<typename T>
vector<complex<T>> fft(vector<complex<T>>&&);
0 голосов
/ 10 сентября 2018

Я думаю, вы запутались, когда дважды использовали имя T. Посмотрите на это так:

template<typename T>
class Vector
{ ... }

template<typename U>
class ComplexVector : public Vector<std::complex<U>>
{ ... }

ComplexVector<float>; // U = float, T = std::complex<float>
0 голосов
/ 12 сентября 2018

ComplexVector<std::complex<T> > является избыточным (вы уже сказали, что это был сложный вектор, верно?).

Используя общее соглашение, вы получите:

class ComplexVector< T > : public Vector< std::complex<T> > {
public:
  typedef std::complex<T> type;//for convenience, etc.
  //...
}

Добавление специализации Vector (как некоторые предлагали), в то же время наследование невозможно напрямую, и необходим (возможно, сложный) рефакторинг, включающий Vector<T> - но в основном он включает перемещение большинства / всех частей в новый общий базовый класс. Это может быть хорошей идеей, если вы, например, уже работаете над Vector<T>.

0 голосов
/ 10 сентября 2018

Шаблонное определение типа

Если вы не хотите иметь специализацию, вы можете просто использовать шаблонный typedef, например, так:

template <typename T>
using ComplexVector = Vector<std::complex<T>>;

Пользователи могут затем использовать ComplexVector<float>, и он будет правильно представлять Vector<std::complex<T>>.
Это очень простое решение, которое не совсем соответствует вашим потребностям. Посмотрите на следующие решения для вашей конкретной проблемы.

Шаблонное наследование

Если ваша цель состоит в том, чтобы изменять только определенные методы, когда T равен std::complex<U>, вы должны наследовать от Vector следующим образом:

template <typename T>
class Vector
{
    public:
    bool is_complex() { return false; }
};

template <typename U>
class ComplexVector : Vector<std::complex<U>>
{
  public:
    bool is_complex() { return true; }
};

Это также позволяет добавлять методы, доступные только для векторов на основе complex.

Добавление метода только для ComplexVector

Если вы хотите иметь возможность «улучшать» ComplexVector, вы можете сделать это так:

#include <iostream>
#include <complex>

template <typename T>
class Vector
{
  public:
    bool is_complex() { return false; }
};

template <typename U>
class ComplexVector : Vector<std::complex<U>>
{
  public:
    bool is_complex() { return true; }
    bool only_for_complex() { return true; } // Only in ComplexVector
};

int main() {
  Vector<float> float_vec;
  ComplexVector<float> complex_vec;

  std::cout << "Is float_vec complex? " << float_vec.is_complex() << "\n";
  std::cout << "Is complex_vec complex? " << complex_vec.is_complex() << "\n";

  // The following line doesn't compile
  // std::cout << "Is only_for_complex method in float_vec? " << float_vec.only_for_complex() << "\n";
  std::cout << "Is only_for_complex method in complex_vec? " << complex_vec.only_for_complex() << "\n";

  return 0;
}

Рабочая демонстрация здесь

Использование черты типа small для проверки типа

Используя некоторые шаблоны, мы можем создать небольшого помощника, чтобы определить, является ли данный тип ComplexVector или нет. Это может пригодиться, если вы хотите безопасно вызывать определенные методы в насыщенной среде шаблона:

// For any type T, value is false.
template <typename T>
struct is_complex_vector
{
    static const bool value = false;
};

// We specialize the struct so that, for any type U,
// passing ComplexVector<U> makes value true
template <>
template <typename U>
struct is_complex_vector<ComplexVector<U>>
{
    static const bool value = true;
};

is_complex_vector<typeof(float_vec)>::value; // is false
is_complex_vector<typeof(complex_vec)>::value; // is true

Вот еще одна небольшая демонстрация, демонстрирующая это поведение на практике
value будет определено при компиляции. Это может позволить вам использовать некоторые трюки SFINAE , чтобы лучше контролировать поток вашей программы. (Или вы можете использовать constexpr, если , если вы используете C ++ 17 .)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...