2

I'm catching up with modern C++, practicing move semantics.

I made a very simple test case:

  • create an instance
  • move-construct a new instance

I noticed that when my instances are destroyed, both destructors are called:

  • the one of the move-constructed instance, where data is a valid pointer
  • the one of the original instance, where the data pointer was deleted and set to nullptr when it was moved

My code deleting a nullptr makes me uncomfortable, here are the questions:

  • is that (deleting nullptr) even a valid operation (i.e. does that result in UB; will it eventually crash my application)?
  • or is my move-constructor / move-assignement operator definition wrong?
  • I found this similar question, but it is still not clear, in particular if deleting a nullptr is a problem. Should I avoid that by checking the pointer in the destructor? If so, it feels like using move semantics causes kind of systematic wrong behavior.

My output for the test (code below) is:

Test 5
        new 0x7512b0
        move_new 0x7512b0
        delete[] 0x7512b0
        delete[] 0

The delete[] 0 output is what grinds my gears.

Here's the main:

#include <iostream>
#include "test5.h"
int main()
{
    std::cout << "Test 5" << std::endl;

    test5 rule5;
    test5 rule5move = std::move(rule5);
    // rule5 = std::move(rule5move);

    return 0;
}

and here's test5.h:

#ifndef TEST5_H
#define TEST5_H

class test5
{
public:
    test5(): data(new float[10]){
        std::cout << "\tnew " << data << std::endl;
        for (int i = 0; i < 10; i++)
            data[i] = float(i);
    }

    ~test5(){
        std::cout << "\tdelete[] " << data << std::endl;
        delete[] data;
    }

    // copy constructor
    test5(const test5& t) : data(new float[10]){
        std::cout << "\tcopy " << data << std::endl;
        std::copy(t.data, t.data + 10, data);
    }

    // copy operator
    test5& operator=(const test5& t){
        std::cout << "\tassign " << data << std::endl;
        std::copy(t.data, t.data + 10, data);
        return *this;
    }

    // move constructor
    test5(test5&& t): data(new float[10]){
        delete[] data;
        data = t.data;
        std::cout << "\tmove_new " << data << std::endl;
        t.data = nullptr;
    }
    // move operator
    test5& operator=(test5&& t){
        delete[] data;
        data = t.data;
        std::cout << "\tmove_assign " << data << std::endl;
        t.data = nullptr;
        return *this;
    }

private:
    float* data;
};

#endif // TEST5_H
L.C.
  • 1,098
  • 10
  • 21

1 Answers1

2

is that (deleting nullptr) even a valid operation (i.e. does that result in UB; will it eventually crash my application)?

Deleting nullptr is a no-op. It is valid. As per the online CPP reference:

If expression evaluates to a null pointer value, no destructors are called, and the deallocation function is not called.

I believe your move constructor and move assignment operator are incorrect. Why use raw pointers anyway?

If you are catching up with modern C++ (as you mentioned), you should be using smart pointers.

P.W
  • 26,289
  • 6
  • 39
  • 76
  • I also read that from C++14 on the destructor is not even called, so my question is... late. Time to learn C++17. – L.C. Mar 29 '19 at 11:32