1

See code below. I comment move constructor and instead of compilation error, copy constructor is now called! Despite the fact that I am using std::move.

How I can be ensure, that my huge object never call copy constructor (for example, I forgot to add move constructor) if I use std::move?

class MyHugeObject
{
public:
    MyHugeObject()
    {
    }
    MyHugeObject(const MyHugeObject& m)
    {
        std::cout << "copy huge object\n";
    }
    // MyHugeObject(MyHugeObject&& m)
    // {
    //     std::cout << "move huge object\n";
    // }
};

MyHugeObject func()
{
    MyHugeObject m1;
    MyHugeObject&& m2 = std::move(m1);
    return std::move(m2);
}

int main()
{
    auto m = func();
    return 0;
}

Output:

copy huge object
curiousguy
  • 8,038
  • 2
  • 40
  • 58
user3324131
  • 923
  • 8
  • 10
  • 5
    Delete the copy constructor `MyHugeObject(const MyHugeObject&) = delete;`. Do the same for the assignment operator. – john Dec 27 '20 at 15:03
  • 3
    fwiw, `std::move` itself does not move anything, it is merely a cast – 463035818_is_not_an_ai Dec 27 '20 at 15:07
  • Copy constructor may be necessary for other code (for example, when using std::vector). – user3324131 Dec 27 '20 at 15:11
  • 2
    @user3324131 non-copyable (but movable) objects can be used with `std::vector` too – 463035818_is_not_an_ai Dec 27 '20 at 15:15
  • _"Copy constructor may be necessary for other code (for example, when using std::vector)"_ What do you mean? You don't want a copy if you forget a move. But you want a copy in some cases. This is all very vague. Be more specific. – JHBonarius Dec 27 '20 at 15:20
  • 4
    P.s. `return std::move(m2);` <-- due to return value optimization (RVO) this code is redundant and bad practice. And `MyHugeObject&& m2 = std::move(m1);` probably also doesn't do what you think it does. – JHBonarius Dec 27 '20 at 15:21
  • 1
    Don't `std::move` into a return. It prevents the copy to be elided. If you remove the move constructor, there is move constructor, so your object will have to be copied if that cannot be elided. – bitmask Dec 27 '20 at 15:26
  • You can't both prevent copying, and also be able to copy in other cases. This makes no sense from a design point of view. – super Dec 27 '20 at 15:27
  • @JHBonarius, I think `return std::move(local_var)` _prevents_ the RVO that would take place if the code was `return local_var`, isn't this the case? – Enlico Dec 27 '20 at 15:33
  • @Enlico yes. like bitmask says. It prevents copy elision. – JHBonarius Dec 27 '20 at 15:34
  • I want C++ to move when I want to move (and nothing else!) and copy when I want to copy. – user3324131 Dec 27 '20 at 15:35
  • @user3324131 then you should use the proper syntax. The compiler cannot read your mind. You should tell it what to do. If you forget to put a `std::move`, that's your fault. you cannot expect the compiler to magically fix it for you. If you need to define a copy-constructor anyhow, you should follow the [Rule of Five](https://stackoverflow.com/a/4172724), which implies you should implement **all 5!** – JHBonarius Dec 27 '20 at 15:40
  • @JHBonarius You right. But the uncertainty with std:: move (you can't say move or copy will be here without some investigation) is extremely annoying. I want a method with 100% move call. – user3324131 Dec 27 '20 at 15:52
  • 1
    Normally, `std::move` *allows* something to be moved if it is moveable, otherwise it will be copied if it is copyable. The thing being *moved to* may-or-may-not actually move from the moved object. So there is move co-operation for both the class allowing moving, the object instance being *moved from*, and the object instance being *moved to*. If any of those parts don't do the moving, then no move happens. You'd need a bit of extra work to support something like a `my_ns::must_move` that squawks if the move doesn't occur. – Eljay Dec 27 '20 at 16:20
  • @Eljay I think ```my_ns::must_move``` is the best solution. Any idea how to write such function? – user3324131 Dec 27 '20 at 16:28
  • 1
    I'm thinking a `my_ns::MoveTracker` class which tracks the state of having been copied or moved by constructor or assignment, and then those state flags can be checked in the destructor if the `my_ns::must_move` has set an `assert_moved` flag set. Invasive, yes, but can be opted in for debug or unit test builds. – Eljay Dec 27 '20 at 16:42

1 Answers1

4

If copy construction of your type is especially expensive or problematic, but not something you want to completely prohibit, you can mark the copy constructor explicit. An explicit copy constructor will not allow copying if not explicitly requested (e.g. you can’t use it to automatically pass arguments by value) but will still be available for explicit copy construction. Meanwhile, implicit move construction can still be allowed.

Sneftel
  • 40,271
  • 12
  • 71
  • 104
  • Of course, if, as the question postulates, he **forgot** to write a move constructor, it's unlikely that he'd **remember** to make the copy constructor explicit. – Pete Becker Dec 27 '20 at 17:49
  • @Pete Becker. Another possible mistake with move constructor: Move constructors will only bind to non-const rvalues. Beware of const! Const variables can not be moved from, since move constructors take a non-const (rvalue) reference. However the compiler will **silently** fall-back to the **copy constructor**. – user3324131 Dec 28 '20 at 08:13