0
votes

There are many threads on Stack Overflow about the interaction between std::move and copy elision, e.g.
What are copy elision and return value optimization?
How to be confident of copy elision / return-value optimization
c++11 Return value optimization or move?

However, all of these threads do not seem to answer the question, whether one can rely on the compiler making the necessary optimizations.

This is not problematic if in the worst case we have a slight decrease in performance, but may be fatal if it conflicts correctness due to possibly duplicated destructor calls.

I have the following situation:

class ObjectContainer {

    Object* obj;

public:

    ObjectContainer(Object* o): obj(o) {}
    ~ObjectContainer() {
        if (obj) delete obj;
    }

    ObjectContainer(ObjectContainer&& other): obj(other.obj) {
        other.obj = nullptr;    
    }

};

ObjectContainer create_container() {
    return ObjectContainer(new Object());
}

What will happen there? Is this guaranteed to work? Or can there be a situation in which the code doesn't compile or, worse, the destructor gets called twice on the same object?

As far as I understand, the compiler does not generate a default copy constructor if I declare a move constructor. Do I have to specify a copy constructor? What happens if I do? Or should I use std::move in the return statement?

I'm going to call above method like this:

void do_stuff() {
    do_some_other_stuff(create_container());
}

void do_some_other_stuff(ObjectContainer const& oc) {
    ...
}
1
I'm sure plenty of answers mention that copy-elision is required in C++17 and merely allowed before.nwp
You are violating the Rule of zero / 5 which will cause lots of other problems. If you fix that it will also make this case behave correctly without copy-elision.nwp
Thanks for your answers. It doesn't really make sense semantically to introduce a copy constructor in this case without adding a reference counter for obj, does it? I didn't know that copy-elision is required from C++17 (I don't remember seeing that statement anywhere in the answers). So that means in C++17 this would be perfectly fine even without the move constructor? However, my code should also work in C++11.cero

1 Answers

1
votes

What will happen there? Is this guaranteed to work? Or can there be a situation in which the code doesn't compile or, worse, the destructor gets called twice on the same object?

As long as you're using automatic variables and temporary objects, C++ guarantees that each object constructed will be destroyed. Elisions will not break the meaning of your code; it's just an optimization.

Copy elision doesn't really elide a copy/move; it elides the existence of an object.

In pre-C++17, your code says to construct an ObjectContainer temporary, then copy/move it into the ObjectContainer return value from the function. A compiler may elide the creation of the temporary, instead constructing the return value object directly from the temporary's given parameters. In doing so, it elides away the copy/move, but it also elides away the destruction of the temporary that it doesn't create.

(in post-C++17, this code says to use the prvalue to initialize the return value object directly, so there's no possibility of a temporary being used here.)

Do I have to specify a copy constructor?

You only need to specify a copy constructor if you want the object to be copyable. If you provide a move constructor to a type, the compiler will automatically = delete the other copy/move constructors/assignment operators unless you explicitly write them.

What happens if I do?

That all depends on what you put into the copy constructor. If you do a standard copy of the value, then you've broken how your type works, since it's supposed to have unique ownership of the allocated pointer.

Or should I use std::move in the return statement?

... why would you? The return statement is given a prvalue; you never need to use std::move on prvalues.