У меня есть вектор, который имеет указатели на элементы из 2 подтипов, доступ к которым осуществляется через интерфейс. Не могу переставить вектор - PullRequest
0 голосов
/ 17 февраля 2020

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

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

Итак. У меня есть std::vector<std::unique_ptr<Interface>>, который может содержать объекты типов Sub1 и Sub2.

В полной версии у меня есть класс, который обходит Interface и работает нормально, поскольку он содержит только Sub2 объекты.

Однако я не могу заставить его работать, когда в векторе есть несколько типов. Ничто из того, что я пробовал за последние несколько часов, ничего не сделало.

#include <iostream>
#include "main.h"
#include <memory>
#include <utility>
#include <vector>
#include <sstream>


//this is the one that won't work
Super Super::transpose() const {
    Super newMatrix = *this;
    int i = 1;
    for(auto& elem : elements){
        *elem = *newMatrix.elements[i];
        i--;
    }
return *this;
}


int main() {
    std::vector<std::unique_ptr<Interface>> newMatrix;
    newMatrix.push_back(std::unique_ptr<Sub1>(new Sub1('x')));
    newMatrix.push_back(std::unique_ptr<Sub2>(new Sub2(1)));
    Super mtx(std::move(newMatrix),2 );
    std::cout << mtx << std::endl;
    mtx.transpose();
    std::cout << mtx;
    return 0;
}

//busywork from here on 
Super::Super(std::vector<std::unique_ptr<Interface>> matrix, int pN){
    elements = std::move(matrix);
    n = pN;
}

std::unique_ptr<Interface> Sub1::clone() const {
    char newVal = val;
    return std::unique_ptr<Sub1>(new Sub1(newVal));
}

std::unique_ptr<Interface> Sub2::clone() const {
    int newVal = i;
    return std::unique_ptr<Sub2>(new Sub2(newVal));
}

Sub1::Sub1(char pVal) {
    val = pVal;
}

Sub2::Sub2(int pI) {
    i = pI;
}

std::string Sub1::toString() const {
    std::stringstream ss;
    ss << val;
    return ss.str();
}

std::string Sub2::toString() const {
    std::stringstream ss;
    ss << i;
    return ss.str();
}

std::ostream &operator<<(std::ostream &os, const Super &matrix) {
    os << "[";
    int i = 0;
    for (auto &elem : matrix.elements) {
        os << elem->toString();
        if (i < matrix.n - 1) {
            os << ",";
        }
        i++;
    }
    os << "]";
    return os;
}

Super::Super(const Super &matrix) {
    n = matrix.n;
    for (auto &elem : matrix.elements) {
        std::unique_ptr<Interface> newElem = elem->clone();
        elements.push_back(std::move(newElem));
    }
}

И вот заголовок

#ifndef EXAMPLE_MAIN_H
#define EXAMPLE_MAIN_H

#include <memory>
#include <vector>


class Interface{
public:
    virtual ~Interface() = default;
    virtual std::unique_ptr<Interface> clone() const = 0;
    virtual std::string toString() const = 0;
};

class Super{
private:
    std::vector<std::unique_ptr<Interface>> elements;
    int n = 2;
public:
    Super(std::vector<std::unique_ptr<Interface>> elements, int n);
    Super(const Super &matrix);
    Super transpose() const;
    friend std::ostream &operator<<(std::ostream &os, const Super &matrix);
};

class Sub1: public Interface{
private:
    char val;
public:
    Sub1(char pVal);
    std::string toString() const override;
    std::unique_ptr<Interface> clone() const override;
};


class Sub2: public Interface{
private:
    int i;
public:
    Sub2(int pI);
    std::string toString() const override;
    std::unique_ptr<Interface> clone() const override;
};


std::ostream &operator<<(std::ostream &os, const Super &matrix);

#endif //EXAMPLE_MAIN_H

Ответы [ 3 ]

2 голосов
/ 17 февраля 2020

Super Super::transpose() const;

Это похоже на функцию, которая не должна изменять *this при вызове, но должна возвращать транспонированную версию *this.

Пример:

#include <algorithm>

Super Super::transpose() const {
    Super newMatrix = *this;
    std::reverse(newMatrix.elements.begin(), newMatrix.elements.end());
    return newMatrix;
}

Если вы хотите транспонировать *this на месте, измените его на:

Super& Super::transpose();

Super& Super::transpose() {
    std::reverse(elements.begin(), elements.end());
    return *this;
}

Если вы должны сначала создать временный объект, вы можете, но вы не можете присвоить разыменованную Interface* другой разыменованной Interface*. Перемещение unique_ptr работает, хотя:

Super& Super::transpose() {
    Super newMatrix = *this;
    size_t s = elements.size();
    for(size_t i = 0; i < s; ++i) {
        elements[i] = std::move(newMatrix.elements[s - i - 1]);
    }
    return *this;
}

Демонстрация

Вы также можете инкапсулировать std::unique_ptr<Interface> в класс-оболочку, чтобы добавить конструктор копирования и оператор присваивания (который не поддерживается std::unique_ptr). Делая это, вы можете значительно упростить ваши другие классы. Вашему классу Super не нужно ничего знать о клонировании. Копирование / перемещение элементов будет работать "из коробки".

Пример оболочки:

class Cell {
public:
    Cell() noexcept = default;                              // empty Cell ctor
    explicit Cell(std::unique_ptr<Interface>&& d) noexcept; // converting ctor

    // rule of five
    Cell(const Cell& rhs);                           // must be implemented
    Cell(Cell&& rhs) noexcept = default;             // handled by unique_ptr
    Cell& operator=(const Cell& rhs);                // must be implemented
    Cell& operator=(Cell&& rhs) noexcept = default;  // handled by unique_ptr
    ~Cell() = default;                               // handled by unique_ptr

    explicit operator bool() const noexcept; // proxy for unique_ptr operator bool
    void reset() noexcept;                   // empty the Cell

    // dereferencing
    Interface& operator*();
    const Interface& operator*() const;
    Interface* operator->();
    const Interface* operator->() const;

    std::ostream& print(std::ostream& os) const;     // calls: os << data->toString()

    // A helper factory to make a Cell of a certain type using the converting ctor
    template<typename T, class... Args>
    static Cell make(Args&&... args) {
        return Cell(std::make_unique<T>(std::forward<Args>(args)...));
    }

private:
    std::unique_ptr<Interface> data{};
};

std::ostream& operator<<(std::ostream& os, const Cell& c); // calls c.print(os)

Конструктор копирования и оператор назначения копирования могут быть реализованы следующим образом:

Cell::Cell(const Cell& rhs) : data(rhs ? rhs.data->clone() : nullptr) {}

Cell& Cell::operator=(const Cell& rhs) {
    if(this != &rhs) data = rhs ? rhs.data->clone() : nullptr;
    return *this;
}

Вот демоверсия с 2D-матрицей с использованием std::vector<std::vector<Cell>>. Такие двухмерные векторы не очень эффективны, но они служат цели демонстрации.

0 голосов
/ 17 февраля 2020

Я придумал эту отвратительную вещь, чтобы исправить это. Мне бы хотелось, чтобы это правильно транспонировало матрицу на месте, но, похоже, сегодня вечером этого не происходит. Поэтому вместо этого я создаю новый объект.

Super::Super(Super &&matrix) {
   elements = std::move(matrix.elements);
   n = matrix.n;
}

//this was the one that wouldn't work
Super Super::transpose() const {
    Super newMatrix = *this;
    Super anotherOne = *this;
    anotherOne.elements.clear();
    for(int i = 1; i > -1; i--){
        anotherOne.elements.push_back(std::move(newMatrix.elements[i]));
    }
    return anotherOne;
 }

int main() {
    std::vector<std::unique_ptr<Interface>> newMatrix;
   newMatrix.push_back(std::unique_ptr<Sub1>(new Sub1('x')));
   newMatrix.push_back(std::unique_ptr<Sub2>(new Sub2(1)));
   Super mtx(std::move(newMatrix),2 );
   std::cout << mtx << std::endl;
   Super aa(std::move(mtx.transpose()));
   std::cout << aa;
   return 0;
}
0 голосов
/ 17 февраля 2020

Ваш Interface не имеет оператора виртуального присваивания, поэтому, естественно, *elem = newMatrix.elements[i]; не будет работать. Я думаю, elem = std::move(newMatrix.elements[i])); должен делать то, что вы хотите.

Редактировать: Извините, Interface будет иметь оператор присваивания, так как он будет создан по умолчанию. Но это не будет виртуальным, поэтому присваивание на Interface ничего не даст, так как не вызывает операторы присваивания для дочерних классов. Вы могли бы написать все это, но это, кажется, не стоит усилий.

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