Проблема с назначением перемещения в C++. Незаконная инструкция: 4

Я пишу простой класс Matrix и определил, среди прочего, перегрузку operator+ и присваивание перемещения. Похоже, когда они взаимодействуют, что-то происходит, но я не могу понять, в чем ошибаюсь. Вот мой код (удалил все лишнее, оставил только то, что нужно для показа бага). Проблемная строка находится в конце, в основном:

#include <iostream>
#define DEF -1

using namespace std;
//----- Matrix -----//
class Matrix{
private:
    float **matrixpp;
    int dim_r;
    int dim_c;

public:
    Matrix(int d_r = DEF, int d_c = 0);   
    Matrix(const Matrix&);
    Matrix(Matrix&&);
    Matrix& operator=(Matrix&&);
    ~Matrix();

    Matrix operator+(const Matrix&);

    void print();
    void fill();

};

//----- Matrix -----//
Matrix::Matrix(int d_r, int d_c){

    if(d_r == DEF){
        do{
            cout << "number of rows: ";
            cin >> dim_r;
            if(dim_r <= 0){
                cout << "ERROR" << endl;
            }
        }
        while(dim_r <= 0);

        do{
            cout << "Number of columns: ";
            cin >> dim_c;
            if(dim_c <= 0){
                cout << "ERROR" << endl;
            }
        }
        while(dim_c <= 0);


    }
    else{
        dim_r = d_r;
        dim_c = d_c;
    }

    matrixpp = new float*[dim_r];

    for(int i = 0; i < dim_r; i++){
        matrixpp[i] = new float[dim_c];
    }

    for(int r = 0; r < dim_r; r++){
        for(int c = 0; c < dim_c; c++){
            matrixpp[r][c] = 0;
        }
    }
}

Matrix::Matrix(const Matrix& tocopy)
:matrixpp(tocopy.matrixpp), dim_r(tocopy.dim_r), dim_c(tocopy.dim_c)
{

    matrixpp = new float*[dim_r];

    for(int i = 0; i < dim_r; i++){
        matrixpp[i] = new float[dim_c];
    }

    for(int r = 0; r < dim_r; r++){
        for(int c = 0; c < dim_c; c++){
            matrixpp[r][c] = tocopy.matrixpp[r][c];
        }
    }
}

Matrix::Matrix(Matrix&& tomove)
:matrixpp(tomove.matrixpp), dim_r(tomove.dim_r), dim_c(tomove.dim_c)
{
    tomove.matrixpp = nullptr;

}

Matrix& Matrix::operator=(Matrix&& tomove){

    cout << "--- MA ---" << endl;
    matrixpp = tomove.matrixpp;
    dim_r = tomove.dim_r;
    dim_c = tomove.dim_c;

    tomove.matrixpp = nullptr;
}

Matrix::~Matrix(){

    if(matrixpp != nullptr){
        for(int i = 0; i < dim_r; i++){
            delete[] matrixpp[i];
        }
        delete[] matrixpp;
    }
}

Matrix Matrix::operator+(const Matrix& m){

    if(this->dim_r == m.dim_r && this->dim_c == m.dim_c){
        Matrix new_m(m.dim_r, m.dim_c);

        for(int r = 0; r < new_m.dim_r; r++){
            for(int c = 0; c < new_m.dim_c; c++){
                new_m.matrixpp[r][c] = this->matrixpp[r][c] + m.matrixpp[r][c];
            }
        }

        return new_m;
    }
    else{
        cout << "ERROR" << endl;
    }
}

void Matrix::print(){
    int temp;

    for(int r = 0; r < dim_r; r++){
        for(int c = 0; c < dim_c; c++){
            cout << matrixpp[r][c] << " ";

        }
        cout << endl;
    }
    cout << endl;
}

void Matrix::fill(){
    float temp;

    for(int r = 0; r < dim_r; r++){
        for(int c = 0; c < dim_c; c++){
            cout << "new value: " << endl;
            this->print();
            cin >> temp;
            matrixpp[r][c] = temp;
            system("clear");
        }
    }
}

//-------- Main -------//
int main(){

    Matrix m0;
    m0.fill();
    Matrix m1(0);

    m1 = m0+m0; // problematic line

    //m1.print();

}

Это дает мне ошибку, когда вызывается назначение перемещения для перемещения результата m0 + m0 в m1.

Если я скомпилирую свой код с помощью g++ на Mac, выдаваемая ошибка будет просто «Недопустимая инструкция: 4», ни больше, ни меньше. Если я делаю это в Linux, выдается ошибка:

В функции-члене «Matrix Matrix::operator+(const Matrix&)»: p.cpp:90:16: ошибка: использование удаленной функции «constexpr Matrix::Matrix(const Matrix&)» return new_m; ^~~~~ p.cpp:9:7: примечание: 'constexpr Matrix::Matrix(const Matrix&)' неявно объявляется удаленным, поскольку 'Matrix' объявляет конструктор перемещения или оператор присваивания перемещения class Matrix{ ^~~~ ~~

Заранее спасибо!


person A.J.    schedule 17.10.2018    source источник
comment
Ваш второй цикл do-while внутри Matrix::Matrix содержит ошибку. Вы вводите dim_c и проверяете dim_r <= 0 (обратите внимание на c и r). Вероятно, это не решит вашу проблему, но, тем не менее, это ошибка. Это риск копирования кода. Кроме того, что произойдет, если я сделаю Matrix m(2, -2);?   -  person Fureeish    schedule 17.10.2018
comment
Пожалуйста, скопируйте и вставьте полную ошибку, напечатанную компилятором.   -  person Yksisarvinen    schedule 17.10.2018
comment
Отредактировано, и спасибо за ошибку   -  person A.J.    schedule 17.10.2018
comment
Правило трех/пяти/ноля   -  person molbdnilo    schedule 17.10.2018
comment
Функции, которые имеют тип возвращаемого значения, отличный от void, должны возвращать что-то на каждом пути. Ваш компилятор должен предупредить вас об этом.   -  person molbdnilo    schedule 17.10.2018
comment
Да, но это предупреждение, а не ошибка. Тем не мение. Цель этой программы заключалась в том, чтобы немного попрактиковаться, поэтому я не стал это исправлять. Но я знаю, что должен   -  person A.J.    schedule 17.10.2018
comment
@ А.Дж. Не игнорируйте предупреждения. Вы должны относиться к предупреждениям как к ошибкам, особенно если вы не знаете, к каким последствиям может привести их игнорирование.   -  person molbdnilo    schedule 17.10.2018
comment
Ошибка довольно подробная, но она сообщает вам, что вы пытались использовать конструктор копирования, который удаляется, поскольку вы предоставили оператор присваивания перемещения. Другими словами, ваш operator+ возвращает объект, а не ссылку rvalue, и этот объект должен быть скопирован, когда вы его возвращаете.   -  person Yksisarvinen    schedule 17.10.2018
comment
@Yksisarvinen, насколько я понял, должно произойти вот что: оператор + возвращает объект, который, будучи возвращаемым значением, является ссылкой на rvalue. Затем назначение перемещения перемещает его в m1. Разве это не то, что должно произойти?   -  person A.J.    schedule 17.10.2018
comment
совет: вставьте значения с плавающей запятой в вектор и используйте правило нуля   -  person sp2danny    schedule 17.10.2018


Ответы (2)


Проблема в том, что при использовании стандарта, предшествующего C++17, нет гарантии исключения копирования, поэтому возврат значения из функции следует семантике инициализации копирования, которая требует определения конструктора перемещения или копирования. Определение собственного оператора присваивания перемещения отключает автоматическую генерацию конструктора (правила генерации CTOR копии класса< /а>). На самом деле предоставленный компилятором конструктор определен, но удален. Чтобы решить эту проблему, необходимо определить конструктор перемещения или копирования или оба в соответствии с правилом пяти (Правило трех/пяти/ноля).

Кроме того, я подозреваю, что первоначальная проблема (недопустимая инструкция) вызвана большим количеством неопределенных поведений в вашем коде (например, отсутствие оператора возврата в вашем операторе присваивания и отсутствие возврата в ветке оператор + else).

person Rafał Górczewski    schedule 17.10.2018
comment
Как я уже сказал, вот упрощенная версия всей моей программы. Там я определил перемещение и копирование, а также все конструкторы и назначения, но проблема все еще оставалась. - person A.J.; 17.10.2018
comment
Что ж. сообщение об ошибке говорит что-то еще. ¯\_(ツ)_/¯ - person Rafał Górczewski; 17.10.2018
comment
Я все еще получаю сообщение об ошибке «Недопустимая инструкция: 4». Во всяком случае, об остальных без возврата, я получаю предупреждение при компиляции. - person A.J.; 17.10.2018
comment
Эти предупреждения действительно важны, вы всегда должны что-то возвращать в функциях с непустым типом возвращаемого значения. - person Rafał Górczewski; 17.10.2018
comment
Да, я знаю это. Но сейчас я просто практиковался с конструктором перемещения и назначением перемещения. Как вы думаете, это может быть причиной проблемы? В противном случае, есть ли у вас какие-либо идеи о том, что может быть причиной ошибки? - person A.J.; 17.10.2018
comment
Да, очень важно не возвращать значение при объявлении другой вещи. Это приводит к неопределенному поведению, и ваша незаконная инструкция может быть вызвана именно из-за этого. - person Rafał Górczewski; 17.10.2018
comment
Ок, тогда попробую. - person A.J.; 17.10.2018
comment
Вы были так правы! Проблема была в присваивании хода: оно ничего не вернуло, хотя должно было, очевидно. Честно даже не заметил. Большое спасибо - person A.J.; 17.10.2018

основная проблема в том, что у вашего оператора = не было возврата.

перегрузите оператор + следующим образом:

Matrix operator+(const Matrix &A ,const Matrix &m)
{
if(A->dim_r == m.dim_r && A->dim_c == m.dim_c){
    Matrix new_m(m.dim_r, m.dim_c);

    for(int r = 0; r < new_m.dim_r; r++){
        for(int c = 0; c < new_m.dim_c; c++){
            new_m.matrixpp[r][c] = A->matrixpp[r][c] + m.matrixpp[r][c];
        }
    }

    return new_m;
}
else{
    cout << "ERROR" << endl;
}

и сделать его другом вашего класса. и в конце оператора = добавьте оператор возврата следующим образом:

Matrix& Matrix::operator=(Matrix&& tomove){
        if(this != &tomove) { 
        cout << "--- MA ---" << endl;
        matrixpp = tomove.matrixpp;
        dim_r = tomove.dim_r;
        dim_c = tomove.dim_c;

        tomove.matrixpp = nullptr;
    }
    return *this;
}
person nader    schedule 17.10.2018
comment
И это должно работать так? В любом случае, почему вместо этого не работает мой способ? - person A.J.; 17.10.2018
comment
Почему? Перегрузки операторов-членов прекрасно работают, когда оба аргумента имеют один и тот же тип. И это не решит проблему отсутствия конструктора копирования. - person Yksisarvinen; 17.10.2018