0

I am working on a simple matrix template class for practice purposes, I have defined my constructor and destructor but on testing the class, I found out that Move and copy operations works without any flaws and I didn't define them. I really do not understand what's going and I don't trust what is happening.

#ifndef MATRIX_H_
#define MATRIX_H_

#include <cstdlib>
#include <iostream>

namespace mat
{
    template<class T>
    class Matrix
    {
        public:
            Matrix(size_t row, size_t column, const T& val)
                : row_{row}, column_{column}, elem_{static_cast<T*>(::operator new(row_ * column_))} 
                {
                    for(size_t i = 0; i != size(); ++i )
                        elem_[i] = val;
                }
            Matrix(size_t row, size_t column)
                :  row_{row}, column_{column}, elem_{static_cast<T*>(::operator new(row_ * column_))} {}
            
            size_t size() const { return row_ * column_; }

            ~Matrix()
            {
                for(size_t i = size(); i != 0; --i)
                {
                    elem_[i].~T();
                    delete elem_;
                }
            }
        private:
            size_t row_;
            size_t column_; 
            T *elem_;
    };
}

#endif
#include <iostream>
#include "Matrix.h"

using namespace mat;

class SomeClass
{
    int s_;
    public:
        SomeClass(int s ) : s_{s} {} 
};

template<class T>
Matrix<T> fn(const Matrix<T> A)
{
    return A;
}

int main()
{
    SomeClass s{1};
    Matrix<SomeClass> my_mat(2, 3, s);
    Matrix<int> my_mat2(2, 3, 4);
    Matrix<SomeClass> my_mat3{2, 3};
    Matrix<int> copy_mat(my_mat2);
}

theProgrammer
  • 226
  • 1
  • 9
  • If you would actually cleanup the allocated memory in your destructor, you might get a crash thanks to double free. You also have UB (undefined behavior) as you call the destructor twice – JVApen Nov 21 '20 at 22:01
  • I actually called the destructor for each elements. Do I explicity need to delete `elem_` – theProgrammer Nov 21 '20 at 22:03
  • 1
    @theProgrammer You didn't construct those objects in the first place. Explicit destructor calls are usually an error, so is using `operator new` manually. – François Andrieux Nov 21 '20 at 22:04
  • Yes, you do if you don't want memory leaks. You also don't need the ~T if you don't do a placement new – JVApen Nov 21 '20 at 22:04
  • I just deleted `elem_`, same result, nothing's change. – theProgrammer Nov 21 '20 at 22:04
  • 2
    @theProgrammer You should read about [the rule of 3/5/0](https://en.cppreference.com/w/cpp/language/rule_of_three). Your assignment operators do not work as well as you think. Your code has Undefined Behavior, and it is just bad luck that it hasn't manifested yet, making it look okay. – François Andrieux Nov 21 '20 at 22:06
  • PS: I would recommend std::make_unique and std::unique_ptr to every user of C++ – JVApen Nov 21 '20 at 22:06
  • What's happening is "undefined behavior". There are multiple, fundamental flaws in the shown code, however that does not guarantee a crash. Maybe not a crash today, but tomorrow the program might crash. Or next week, or whenever [demons finally decide to fly out of your nose](http://www.catb.org/jargon/html/N/nasal-demons.html). If you don't want that to happen, you'll need to address and eliminate the undefined behavior. – Sam Varshavchik Nov 21 '20 at 22:07
  • And another UB, you are indexing out of bounds – JVApen Nov 21 '20 at 22:07
  • @JVApen thanks, I just saw it i the destructor. – theProgrammer Nov 21 '20 at 22:11

1 Answers1

2

If you don't declare a special member function, then sometimes the compiler implicitly declares and/or defines the functions.

See the table in this answer for more detail.

Your code causes undefined behaviour in several ways; one possible upshot of undefined behaviour is appearing to work correctly -- for now. You should run into more obvious issues when using a matrix element type which has a destructor with side-effects.


Some problems:

  • ::operator new expects a number of bytes, but you provided a number of elements.
  • In the constructor you assign to elements that have not been constructed. (In C++20 this might work for trivial element types, but not when the constructor has side-effects). You should use placement-new instead of the assignment operator.
  • The constructor version taking no initial value leaves the memory uninitialized (and no objects created), you still have to call placement-new on every element in this version.
  • In the destructor you call ~T() for elements that have never had a constructor called. Also it seems to have an off by one error as you start by calling destructor for elem_[size()], and don't call it for elem[0].
  • delete elem; is called size() number of times, it should be called zero times. Use ::operator delete outside the loop to deallocate memory. (delete expression should be paired with new expression, while ::operator delete function paired with ::operator new function).
  • The copy constructor, move constructor, copy assignment, and move assignment, should be defined by you and provide correct behaviour. (Which in the case of copy-operations will be calls to placement-new for each element, if you are intending to continue with the plan of individually destroying each element).
M.M
  • 138,810
  • 21
  • 208
  • 365
  • Thanks, I would be glad if you could point out those undefined behaviors. Secondly, would defining my copy and move constructors alongside the assignment operators curb the UBs. – theProgrammer Nov 21 '20 at 22:19
  • 1
    @theProgrammer As a general rule, you should not be managing memory manually. Use `std::vector` instead and most of the code for `Matrix` can be removed. Copy, move and destruction will all be handled correctly by the default compiler generated versions. All of the other problems mentioned would disappear. – François Andrieux Nov 21 '20 at 22:35
  • @theProgrammer started a list – M.M Nov 21 '20 at 22:38
  • @FrançoisAndrieux I'm guessing they want to reinvent the wheel for educational purposes, and also use individual element construction (as `vector` does) to avoid default-constructing elements unecessarily, e.g. your suggestion won't work if `T` is not default-constructible – M.M Nov 21 '20 at 22:40
  • @M.M Maybe, but that would seem like a strange requirement for a `Matrix` class which is mathematical in nature. It would imply a non-default-constructible numeric type. – François Andrieux Nov 21 '20 at 22:42
  • @M.M Thanks, You saved me from future headaches. I'm fairly new to dynamic allocation. – theProgrammer Nov 21 '20 at 22:54
  • @theProgrammer if you do not need to support non-default-constructible element types, then as François says it'd be a whole lot simpler to use array new and delete (either by vector, or by something like `unique_ptr`) – M.M Nov 21 '20 at 22:58