Полиморфизм времени выполнения C++11 и перегрузка операторов
Допустим, я пытаюсь реализовать некоторый математический векторный класс.
В качестве векторного интерфейса будет использоваться несколько мест: вектор на основе массива, матрицы возвращают столбцы и строки в виде объектов векторного интерфейса и т. Д.
Я хотел бы перегрузить операторы +,- для моих векторов. Каждый оператор должен возвращать новый построенный объект некоторого класса векторной реализации.
Но, как вы знаете, перегрузка оператора должна возвращать значение или ссылку. Я не могу вернуть значение, так как мне нужен полиморфизм времени выполнения, поэтому у меня остались ссылки. Но чтобы иметь ссылку, которая не умирает после того, как объект вызова функции должен быть создан в куче.
Так как мне справиться с ситуацией?
PS Я мог бы создать shared_ptr и вернуть ссылку на содержащее значение, но это не выглядит хорошей практикой.
typedef unsigned int vector_idx_t;
template <class T, vector_idx_t size>
class vector {
public:
virtual ~vector();
virtual T& operator[](const vector_idx_t idx) = 0;
virtual vector<T, size>& operator+ (const T& a) const = 0;
virtual vector<T, size>& operator- (const T& a) const = 0;
virtual vector<T, size>& operator* (const T& a) const = 0;
virtual vector<T, size>& operator/ (const T& a) const = 0;
virtual vector<T, size>& operator+ (const vector<T, size>& vec2) const = 0;
virtual vector<T, size>& operator- (const vector<T, size>& vec2) const = 0;
};
template <class T, vector_idx_t size>
class array_vector: public vector<T, size> {
private:
std::array<T, size> m_elements;
public:
array_vector();
array_vector(std::array<T, size> elements);
array_vector(const vector<T, size>& vec2);
array_vector(std::initializer_list<T> elems);
virtual ~array_vector();
virtual T& operator[](const vector_idx_t idx) {
return m_elements[idx];
}
virtual vector<T, size>& operator+ (const T& a) const {
std::array<T, size> e;
for (vector_idx_t i = 0; i < size; ++i) {
e[i] = m_elements[i] + a;
}
auto v = std::make_shared<array_vector<T, size>>(elems);
return *v;
}
};
2 ответа
Я предлагаю небольшую модификацию вашего дизайна для учета полиморфного характера реализации.
- Не делай
vector
полиморфный. - Использовать
Data
класс, чтобы содержать конкретные детали реализацииvector
, - Делать
Data
полиморфный.
Это позволит вам вернуться vector
s по значению или по ссылке, в зависимости от интерфейса.
Полиморфизм по подтипам не является решением всех проблем. Я понимаю, что вы пытаетесь сделать, но я не совсем понимаю, почему решения по полиморфному шаблону недостаточно, и вам нужно иметь виртуальные операторы (которые вообще плохо сочетаются с полиморфизмом по подтипу).
Вы хотите иметь возможность определять операции над смешанными типами векторов, чтобы можно было вычислять результаты между реальными контейнерами и прокси для контейнеров.
Прежде всего, это должно потребовать, чтобы у вас был базовый конечный тип, который вам нужен: прокси для столбца матрицы - это не реальный контейнер, а скорее представление контейнера, поэтому добавление двух из них должно вернуть реальный контейнер (например, a контейнер подкреплен фактическим std::array
?).
Подобным дизайном может управлять что-то вроде
template<typename ContainerType, typename ElementType>
class vector_of : public ContainerType
{
public:
vector_of(const ContainerType& container) : ContainerType(container) { }
vector_of<ContainerType, ElementType> operator+(const ElementType& a) const
{
vector_of<ContainerType, ElementType> copy = vector_of<ContainerType,ElementType>(*this);
std::for_each(copy.begin(), copy.end(), [&a](ElementType& element) { element += a; });
}
template<typename T>
vector_of<ContainerType, ElementType> operator+(const vector_of<T, ElementType>& a) const
{
vector_of<ContainerType, ElementType> copy(*this);
auto it = copy.begin();
auto it2 = a.begin();
while (it != copy.end() && it2 != a.end())
{
*it += *it2;
++it;
++it2;
}
return copy;
}
};
Хитрость в том, что operator+ - это метод шаблона, который принимает общий контейнер ElementType
элементы. Код предполагает, что такого рода контейнеры обеспечивают begin
а также end
методы, которые возвращают итератор (который в любом случае является разумным выбором, потому что он хорошо работает с STL).
С вами можно делать такие вещи, как:
class MatrixRowProxy
{
private:
int* data;
size_t length;
public:
MatrixRowProxy(int* data, size_t length) : data(data), length(length) { }
int* begin() const { return data; }
int* end() const { return data + length; }
};
vector_of<std::array<int, 5>, int> base = vector_of<std::array<int, 5>, int>({ 1, 2, 3, 4, 5 });
vector_of<std::vector<int>, int> element = vector_of<std::vector<int>, int>({ 2, 3, 4, 5, 6 });
int* data = new int[5] { 10, 20, 30, 40, 50};
vector_of<MatrixRowProxy, int> proxy = vector_of<MatrixRowProxy, int>(MatrixRowProxy(data, 5));
auto result = base + element + proxy;
for (const auto& t : result)
std::cout << t << std::endl;
Таким образом, вы можете добавлять гетерогенные виды векторов без необходимости каких-либо virtual
метод.
Конечно, эти методы требуют создания нового результирующего объекта в методах. Это делается путем копирования этого в новый vector_of<ContainerType, ElementType>
, Ничто не мешает вам добавить третий аргумент шаблона, такой как VectorFactory, который позаботится об этом, чтобы вы могли использовать векторы, которые являются только обертками, также в LHS таких операторов.