If you std::move an object can you delete the original safely? Should you?

Viewed 78

I have difficulty understanding std::move behavior and would like to know whether it is necessary to manually call delete for newStudentDan after "addStudent" in an example like below, or it will be a memory leak.

#include <iostream>
#include <vector>
#include <memory>
using namespace std;

class Student {
    public:
        Student(std::string name): name_{name} {}
        
        void addParent(std::string name) {
            parent_ = name;
        }
        
        
    private:
        std::string name_;
        std::string parent_;
};

class School {
    public:
        //constructor
        School(): name_{"default"} {}

        Student* addStudent(Student student);
        Student* addStudent(std::unique_ptr<Student>&& ptr, int);
        
    private:
        std::string name_;
        std::vector<std::unique_ptr<Student>> students_;
};

Student* School::addStudent(Student student) {
    return this->addStudent(std::unique_ptr<Student>{ new Student{std::move(student)}}, 1);
}

Student* School::addStudent(std::unique_ptr<Student>&& ptr, int) {
    Student* retval{ptr.get()};
    students_.push_back(std::move(ptr));
    return retval;
}

int main() {
    School newSchool;
    Student* newStudentDan = new Student("Dan");
    newStudentDan->addParent("Lisa");
    newSchool.addStudent(*newStudentDan);
    //...continues
    return 0;
}
1 Answers

I think your confusion stems from two related, but separate concepts: storage duration and object lifetime. Moving an object, just like copying, causes a new object to be created, without ending the lifetime of the original. On the other hand, a memory leak is a failure to deallocate memory that is no longer needed, which is indeed the case with newStudentDan in your example.

The direct answer to your question is that yes, you should always delete any object allocated with new, regardless of what you do with it.

Move semantics can be demonstrated without the use of dynamic allocations:

Student& School::addStudent(Student student) {
    students_.push_back(std::move(student));
    return students_.back();
}

int main() {
    School newSchool;
    Student newStudentDan {"Dan"};
    newStudentDan.addParent("Lisa");
    newSchool.addStudent(std::move(newStudentDan));
}

Perhaps the easiest way to think about it is this: std::move does not actually move anything, it allows the compiler to select functions that may "steal" the object's resources, such as by invoking its move constructor or move-assignment operator. These may not be available, in which case the copy operations are used if available.

In the snippet above, Student has an (implicit) move constructor, so it will be the one used to create the new object from newStudentDan. The lifetime of newStudentDan continues until the end of main() and it will still have some memory associated with it, even though its actual data are now owned by the vector. But because it's now a stack variable (it has "automatic storage duration"), destruction and deallocation occur automatically.

Note that not even move constructors do any moving either, in the sense of relocating data in memory. For example, as this detailed answer explains, the move constructor for std::unique_ptr simply copies a pointer and sets the source pointer to nullptr, without touching the object itself. For better or worse, the word "move" is just a term that indicates the type of semantics where the source object will not necessarily be equivalent to the target object.

Related