vendredi 29 mars 2019

Move semantics: why is the destructor called on the moved instance and is that a problem?

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

Aucun commentaire:

Enregistrer un commentaire