Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid triggering this kind of copy constructor in c++11?

Tags:

c++

c++11

c++17

I want to create an object in a function and use it outside.

I write the following code under the c++17 standard, it seems ok.

#include <iostream>

struct Vector2 {
    Vector2() = default;
    Vector2(int x, int y) : x(x), y(y) {}
    Vector2(const Vector2 &) = delete;
    Vector2 &operator=(const Vector2 &) = delete;

    int x = 0;
    int y = 0;
};

Vector2 newVec(int x, int y) {
    return Vector2(x, y);
}

int main() {
    auto v = newVec(1, 2);
    std::cout << v.x * v.y << std::endl;
    return 0;
}

But when I switched to c++11 standard, I couldn't compile it.

note: 'Vector2' has been explicitly marked deleted here Vector2(const Vector2 &) = delete;

I think I constructed a temporary Vector2 object in the newVec function. When return, Vector2 call copy constructor with the temporary variable as the argument. But I have marked copy constructor 'delete', so it can't be compiled.

My question is what is the equivalent code under the C++11 standard? What did c++17 do to avoid this copy constructor?

like image 562
0x11901 Avatar asked Nov 21 '25 14:11

0x11901


1 Answers

What did c++17 do to avoid this copy constructor?

This:

auto v = newVec(1, 2);

was one of the motivating cases for a language feature called "guaranteed copy elision." The example from that paper:

auto x = make(); // error, can't perform the move you didn't want,
                 // even though compiler would not actually call it

Before C++17, that expression always copy-initialization. We deduce the type with auto, that gives us Vector2, and then we're trying to construct a Vector2 from an rvalue of type Vector2. That's the usual process - enumerating constructors, etc. The best match is your copy constructor, which is deleted, hence the whole thing is ill-formed.

Sad face.

In C++17, this is totally different. Initializing from a prvalue of the same type doesn't try to find a constructor at all. There is no copy or move. It just gives you a Vector2 that is the value of newVec(1, 2), directly. That's the change here - it's not that in C++17 that copy-initialization works, it's more that it's not even the same kind of initialization anymore.


My question is what is the equivalent code under the C++11 standard?

There is no way at all to construct a non-movable object like this. From the same paper:

struct NonMoveable { /* ... */ };
NonMoveable make() { /* how to make this work without a copy? */ }

If you want a function like make() (newVec() in your example) to work, the type has to be, at least, movable. That means either:

  • adding a move constructor
  • undeleting your copy constructor
  • if none of the above is possible, put it on the heap - wrap it in something like unique_ptr<Vector2>, which is movable

Or you pass into make() your object and have the function populate it internally:

void make(NonMoveable&);

This ends up being less compose-able, but it works. As written, it requires your type to be default constructible but there are ways around that as well.

like image 100
Barry Avatar answered Nov 24 '25 05:11

Barry