1

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;
}
Dorito Johnson
  • 217
  • 1
  • 11
  • 1
    Does this even compile? You're putting `Student*`s into a `vector`. – Stephen Newell Sep 09 '22 at 21:02
  • Your thouughts are ok, but ... pointers? – Ted Lyngmo Sep 09 '22 at 21:03
  • @StephenNewell I updated it to compile, it was just an example there were some errors – Dorito Johnson Sep 09 '22 at 21:36
  • @TedLyngmo Which thoughts are you referring to? That the student can/should be deleted manually after adding to school? – Dorito Johnson Sep 09 '22 at 21:37
  • If you don't manually call `delete newStudentDan;`, what exactly do you think will delete it for you? Even aside from the fact that moved-from objects don't delete themselves automatically, you move from a _copy_ of `*newStudentDan` and never modify the original. – Nathan Pierson Sep 09 '22 at 21:47
  • 1
    The probability that no dynamic `Student` management via `std::unique_ptr`, or otherwise, is necessary for your task, is very, very high. I see *nothing* a simple `std::vector` couldn't address in any of this. Unless there is some secret master plan of managing polymorphic derivations of `Student` we're not privy to, this is a classic KISS example. – WhozCraig Sep 09 '22 at 21:53
  • `std::move()` gets called on the object named `student` (inside `addStudent()`). `student` is its own separate object (since in `addStudent(Student student)` the argument is passed by-value) and therefore is unrelated to the object that is pointed to by `newStudentDan`. Since you allocated `newStudentDan` with `new`, to avoid a memory-leak you'll need to also delete it with `delete` (or better yet, change your `main()` to place `newStudentDan` on the stack rather than allocating him with `new`) – Jeremy Friesner Sep 09 '22 at 22:09
  • 1
    If you never use new, you never have to worry about if/when you should call delete. Use a smart pointer. – Taekahn Sep 09 '22 at 23:20

1 Answers1

1

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.

sigma
  • 2,758
  • 1
  • 14
  • 18