C++ эффективная реализация классов-оболочек
У меня есть проект, который широко использует (с высокой частотой) ограниченный набор ключевых операций линейной алгебры, таких как умножение матриц, инверсия матриц, сложение и т. Д. Эти операции реализованы с помощью нескольких библиотек линейной алгебры, которые я хотел бы сравнить без необходимости перекомпиляции кода бизнес-логики для согласования различных подходов этих различных библиотек.
Я заинтересован в выяснении того, что является самым умным способом размещения класса-обертки в качестве абстракции во всех этих библиотеках, чтобы стандартизировать эти операции в отношении остальной части моего кода. Мой нынешний подход основан на шаблоне Curiously Recurring Template Pattern и том факте, что C++11 gcc достаточно умен, чтобы встроить виртуальные функции в нужных условиях.
Это интерфейс оболочки, который будет доступен бизнес-логике:
template <class T>
class ITensor {
virtual void initZeros(uint32_t dim1, uint32_t dim2) = 0;
virtual void initOnes(uint32_t dim1, uint32_t dim2) = 0;
virtual void initRand(uint32_t dim1, uint32_t dim2) = 0;
virtual T mult(T& t) = 0;
virtual T add(T& t) = 0;
};
А вот реализация этого интерфейса с использованием, например, Armadillo
template <typename precision>
class Tensor : public ITensor<Tensor<precision> >
{
public:
Tensor(){}
Tensor(arma::Mat<precision> mat) : M(mat) { }
~Tensor(){}
inline void initOnes(uint32_t dim1, uint32_t dim2) override final
{ M = arma::ones<arma::Mat<precision> >(dim1,dim2); }
inline void initZeros(uint32_t dim1, uint32_t dim2) override final
{ M = arma::zeros<arma::Mat<precision> >(dim1,dim2);}
inline void initRand(uint32_t dim1, uint32_t dim2) override final
{ M = arma::randu<arma::Mat<precision> >(dim1,dim2);}
inline Tensor<precision> mult(Tensor<precision>& t1) override final
{
Tensor<precision> t(M * t1.M);
return t;
}
inline Tensor<precision> add(Tensor<precision>& t1) override final
{
Tensor<precision> t( M + t1.M);
return t;
}
arma::Mat<precision> M;
};
Вопросы:
- Имеет ли смысл использовать CRTP и встраивание в этом сценарии?
- Можно ли это улучшить с точки зрения оптимизации производительности?
Как указано в ответе, использование полиморфизма здесь немного странно из-за шаблонов базового класса. Вот почему я думаю, что это все еще имеет смысл:
Вы заметите, что базовый класс называется "Tensor", а не чем-то более конкретным, например "ArmadilloTensor" (в конце концов, базовый класс реализует методы ITensor с использованием методов Armadillo). Я сохранил имя как есть, потому что согласно моему нынешнему замыслу, использование полиморфизма больше связано с ощущением формализма, чем с чем-либо еще. План состоит в том, чтобы код проекта был осведомлен о классе Tensor, который предлагает функции, указанные в ITensor. Для каждой новой библиотеки, которую я хочу сравнить, я бы просто написал новый класс "Tensor" в новом модуле компиляции, упаковал результаты компиляции в архив.a, а при выполнении теста сравнительного анализа связал код бизнес-логики с этим библиотека. Переключение между различными реализациями становится вопросом выбора реализации Tensor для связи. Для базового кода все равно, реализованы ли методы Tensor Armadillo или что-то еще. Преимущества: позволяет избежать наличия кода, который знает о каждой библиотеке (все они независимы), и не требуется никаких изменений времени компиляции в базовом коде для использования новой реализации. Итак, почему полиморфизм? Я просто хотел как-то формализовать функции, которые должны быть реализованы любой новой библиотекой, добавленной в тест. В действительности, базовый код будет работать с ITensors в параметрах функции, но затем потенциально static_cast их до Tensors в самих телах методов.
1 ответ
Возможно, я что-то здесь упустил, или вы не показали достаточно подробностей.
Вы используете полиморфизм. Как определено в его названии, речь идет об одном типе, принимающем разные формы (разное поведение). Таким образом, у вас есть интерфейс, который принимается кодом пользователя, и вы можете предоставить различные реализации этого интерфейса.
Но в вашем случае у вас нет разных реализаций одного интерфейса. Ваш ITensor
Шаблон генерирует разные классы и каждую финальную реализацию вашего Tensor
происходит из определенной базы.
Учтите, что ваш код пользователя выглядит примерно так:
template<typename T>
void useTensor(ITensor<T>& tensor);
и вы можете предоставить свой Tensor
реализация. Это почти так же, как
template<typename T>
void useTensor(T& tensor);
просто без CRTP и виртуальных звонков. Теперь каждая оболочка должна реализовывать некоторый набор функций. Существует проблема в том, что этот набор функций не определен явно. Компилятор здесь очень помогает, но он не идеален. Вот почему мы все с нетерпением ждем, чтобы получить Концепции в следующем стандарте.