Полиморфизм времени выполнения 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 ответа

Я предлагаю небольшую модификацию вашего дизайна для учета полиморфного характера реализации.

  1. Не делай vector полиморфный.
  2. Использовать Data класс, чтобы содержать конкретные детали реализации vector,
  3. Делать Data полиморфный.

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

Полиморфизм по подтипам не является решением всех проблем. Я понимаю, что вы пытаетесь сделать, но я не совсем понимаю, почему решения по полиморфному шаблону недостаточно, и вам нужно иметь виртуальные операторы (которые вообще плохо сочетаются с полиморфизмом по подтипу).

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

Прежде всего, это должно потребовать, чтобы у вас был базовый конечный тип, который вам нужен: прокси для столбца матрицы - это не реальный контейнер, а скорее представление контейнера, поэтому добавление двух из них должно вернуть реальный контейнер (например, 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 таких операторов.

Другие вопросы по тегам