Программа с классом, который использует оператор << и печатает неправильное значение c ++ - PullRequest
0 голосов
/ 18 мая 2018

Я сделал матричный класс.Это некоторая часть.

Math::Matrix::Matrix(int row, int col, double * mat)
{
    this->mat = new double[(this->row = row) * (this->col = col)];
    for (int i = 0; i < row * col; i++)
    {
        this->mat[i] = mat[i];
    }
}


Math::Matrix::~Matrix()
{
    if (this->mat != nullptr)
    {
        delete[] this->mat;
    }
}

const Math::Matrix Math::Matrix::multiply(Matrix & A) const
{
    if (!this->is_multipliable(A))
    {
        throw new std::exception("Math::Matrix::multiply : cannot multiply!");
    }
    Matrix B = Matrix(this->row, A.col);
    for (int k = 0; k < this->col; k++)
    {
        for (int i = 0; i < this->row; i++)
        {
            for (int j = 0; j < A.col; j++)
            {
                B.mat[i * A.col + j] = this->mat[i * this->col + k] * A.mat[k * A.col + j];
            }
        }
    }
    return B;
}

std::ostream & Math::operator<<(std::ostream & os, const Matrix & m)
{
    for (int i = 0; i < m.row; i++)
    {
        for (int j = 0; j < m.col; j++)
        {
            os << m.mat[i*m.col + j] << ' ';
        }
        os << '\n';
    }
    return os;
}

Это часть класса матрицы, которую я написал, и когда я пытаюсь

int main()
{
    A = Math::Matrix(2, 3, new double[6]{ 1, 2, 3, 4, 5, 6 });
    B = Math::Matrix(3, 4, new double[12]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2 });
    std::cout << A << std::endl;
    std::cout << B << std::endl;
    std::cout << A.multiply(B) << std::endl;
}

в основном, программа выдаст несколько глупыхчисла, такие как

-1.45682e+144 -1.45682e+144 -1.45682e+144

-1.45682e+144 -1.45682e+144 -1.45682e+144


-1.45682e+144 1.07458e-255 3.02386e-294 1.41763e-311

2.122e-314 -7.84591e+298 -1.45684e+144 1.87482e-310

2.9803e-294 -1.45682e+144 -1.45682e+144 -1.45682e+144


-1.45682e+144 -1.45682e+144 -1.45682e+144 -1.45682e+144

-1.45682e+144 -1.45682e+144 -1.45682e+144 -1.45682e+144

(без отладки)

или

1 2 3

4 5 6


-1.45682e+144 -1.45682e+144 -1.45682e+144 -1.45682e+144

-1.45682e+144 -1.45682e+144 -1.45682e+144 -1.45682e+144

3.67842e-293 8.81477e-310 3.6647e-293 -1.45682e+144


-1.45682e+144 -1.45682e+144 -1.45682e+144 -1.45682e+144

-1.45682e+144 -1.45682e+144 -1.45682e+144 -1.45682e+144

(отладка)

, а затем выдает исключение.

В режиме отладки говорится, что «wntdll.pdb содержит отладочную информацию, необходимую для поиска источника для модуля ntdll.dll».

Я использую Visual Studio 2017.

В чем проблема этого кода?

Ответы [ 4 ]

0 голосов
/ 18 мая 2018

Хорошо, проблема, вероятно, в том, что вы не показывали операторы копирования / перемещения.

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

Так что это может быть приемлемо (я говорю приемлемо, не хорошо, потому что, если вы не реализуете универсальный контейнер, вы должны избегать необработанного динамического выделения памятии полагаться только на стандартные контейнеры):

#include <iostream>

namespace Math {

    class Matrix {
    public:
        int row;
        int col;
        double *mat;              // beware rule of 3/5

        Matrix(int row, int col, double *mat);
        ~Matrix();
        Matrix(const Matrix&) = delete;    // explicitely delete what we don't define
        Matrix(Matrix&&) = delete;
        Matrix operator = (const Matrix&) = delete;
        Matrix operator = (Matrix&&) = delete;
    };
}
Math::Matrix::Matrix(int row, int col, double * mat)
{
    this->mat = new double[(this->row = row) * (this->col = col)];
    for (int i = 0; i < row * col; i++) {
        this->mat[i] = mat[i];
    }
}


Math::Matrix::~Matrix()
{
    if (this->mat != nullptr)
    {
        delete[] this->mat;
    }
}

std::ostream & operator<<(std::ostream & os, const Math::Matrix & m)
{
    for (int i = 0; i < m.row; i++)
    {
        for (int j = 0; j < m.col; j++)
        {
            os << m.mat[i*m.col + j] << ' ';
        }
        os << '\n';
    }
    return os;
}
int main(){

    double *m = new double[6]{1,2,3,4,5,6};  // store the automatic memory pointer ...
    Math::Matrix mat(2, 3, m); // only uses the direct ctor
    delete[] m;                              // to be able to delete it!
    std::cout << mat;

    return 0;
}

Но когда вы пишете:

A = Math::Matrix(2, 3, new double[6]{ 1, 2, 3, 4, 5, 6 });
  • , вы сначала выделяете память для массива из 6 двойных, которые никогда не будут освобождены с тех порвы не перемещаете указатель -> утечка памяти гарантируется
  • , тогда вы почти правильно строите временную матрицу
  • , которую вы назначаете для уже существующего объекта ...

Оператор перемещения по умолчанию копирует элемент mat из темпаПопробуйте матрицу A, и тогда временная матрица будет уничтожена, освобождая указатель памяти на mat member -> висячий указатель, гарантированный

Этот висячий указатель является причиной неопределенного поведения и странных выходных данных.

Кстати, моя версия с удаленными операторами присваивания вызовет ошибку компиляции в этой строке, что является предупреждением красного света: остерегайтесь, что-то скоро сломается около этой точки ...

TL / DR: если выможете избежать прямого управления выделенной памятью, а если вы не можете этого избежать, то реализуйте конструкторы копирования / перемещения и операторы присваивания или явно удалите те, которые вы не определили.

0 голосов
/ 18 мая 2018

Когда вы используете сырые указатели, вы должны понимать, как это работает довольно хорошо.Одно из самых важных правил - правило трех , ваш класс определенно нарушает это.Таким образом, эти строки кода имеют проблему:

A = Math::Matrix(2, 3, new double[6]{ 1, 2, 3, 4, 5, 6 });
B = Math::Matrix(3, 4, new double[12]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2 });
Matrix B = Matrix(this->row, A.col);

ваши объекты содержат висячие указатели на удаленную память после уничтожения временных файлов.У вас также есть проблема с утечкой памяти, когда вы передаете указатель на динамически выделенную память, а затем отбрасываете ее.Вам лучше использовать std::vector<double> внутри вашего класса, который будет выполнять все выделения для вас и уже обеспечит правильные конструкции копирования / перемещения и операторы присваивания, а также деструктор для вас.Вам даже не потребуется реализовывать деструктор и использовать один по умолчанию.

0 голосов
/ 18 мая 2018

Давайте сделаем предположение о том, как вы определили Matrix, а затем перепишите его, чтобы оно было правильным по умолчанию .

namespace Math {

class Matrix {
    double * mat; // Bad. Owning raw pointer
    int col, row; // Questionable. std::size_t is the normal type for an index
public:
    Matrix(int row, int col, double * mat); // Bad. Owning raw pointer
    ~Matrix(); // Will be un-necessary under the rule of 0
    const Matrix multiply(Matrix & A) const; // this is usually named "operator *"
    friend std::ostream & operator<<(std::ostream & os, const Matrix & m); // Good
};
}

А вот как мы приведем в порядок этоup

namespace Math {

class Matrix {
    std::vector<double> mat; 
    std::size_t col, row; 

public:
    Matrix(std::size_t row, std::size_t col) 
     : row(row), col(col), mat(row * col, 0) 
    {}

    template <typename InputIterator>
    Matrix(std::size_t row, std::size_t col, InputIterator it) // copy from any sequence of doubles
     : Matrix(row, col) // delegate to 2-arg constructor to initialises fields
    {
        std::copy_n(it, row * col, mat.begin());
    }

    double & data(std::size_t c, std::size_t r) // Convinience
    { return mat[(r * col) + c]; }

    double data(std::size_t c, std::size_t r) const // Overload for const
    { return mat[(r * col) + c]; }

    friend Matrix operator*(const Matrix & lhs, const Matrix & rhs)
    {
        if (!lhs.is_multipliable(rhs))
        {
            // MSVC includes this as a non-standard extension
            // std::runtime_error is a portable replacement
            throw new std::exception("Math::Matrix::multiply : cannot multiply!");
        }
        Matrix result = Matrix(lhs.row, rhs.col);
        for (int k = 0; k < lhs.col; k++)
        {
            for (int i = 0; i < lhs.row; i++)
            {
                for (int j = 0; j < rhs.col; j++)
                {
                    result.data(i, j) += lhs.data(i, k) * rhs.data(k, j);
                }
            }
        }
        return result;
    }

    friend std::ostream & operator<<(std::ostream & os, const Matrix & m);    
    friend std::istream & operator>>(std::istream & is,       Matrix & m);    
};
}

Обратите внимание, что std::exception не определен как имеющий const char * принимающий конструктор

0 голосов
/ 18 мая 2018

Эта строка плохая идея

A = Math::Matrix(2, 3, new double[6]{ 1, 2, 3, 4, 5, 6 });

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

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

Определите, по крайней мере, конструктор копирования и оператор присваивания, рекомендуется добавить конструктор перемещения, перемещение и конструктор по умолчанию.Конструктор, использующий массив, может быть создан с использованием std::initializer_list

...