0

I have this code

class MyString {
    public:
        MyString();
        MyString(const char*);
        MyString(const String&);
        MyString(String&&) noexcept;
        ...
};

String::String()
{
    std::cout << "default construct!" <<std::endl;
}

String::String(const char* cb)
{
    std::cout << "construct with C-char!" <<std::endl;
    ...
}


String::String(const String& str)
{
    std::cout << "copy construct!" <<std::endl;
    ...
}

String::String(String&& str) noexcept
{
    std::cout << "move construct!" <<std::endl;
    ...
}

In main()

MyString s1(MyString("test"));

I thought result would be this:

construct with C-char! <---- called by MyString("test")
move construct! <---- called by s1(...)

But what I get was this:

construct with C-char! <---- maybe called by MyString()

The steps what I thought

  1. MyString("test") construct a rvalue by using the constructor with char*
  2. Construct s1(arg)
  3. Because of arg is a rvalue, s1 should construct by move constructor But I find move constructor won't be called without std::move.

Why is that happening?
How to use move constructor without std::move()?

Compiler:
Gnu C++ 9.3.0

Touki Liu
  • 51
  • 5
  • I believe copy elision is mandatory in this case. – Mechap Oct 27 '21 at 08:14
  • 1
    @Mechap: only since C++17. – Jarod42 Oct 27 '21 at 08:15
  • 4
    The concept you're missing is "copy elision". See [this question](https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization). – molbdnilo Oct 27 '21 at 08:15
  • Your reasoning is correct, You can have expected behavior with `-fno-elide-constructor` flag to turn-off that optimization. – Jarod42 Oct 27 '21 at 08:18
  • @Jarod42 Only in old versions of the standard where it is just an optimisation. You cannot disable the behaviour in C++17 or later. – eerorika Oct 27 '21 at 08:25
  • @eerorika: OP tags question as C++11 :-) (I expect `-fno-elide-constructors` to avoid non-mandatory elision in C++17 (as **N**RVO) though, (which is indeed not the OP case which is mandatory)). – Jarod42 Oct 27 '21 at 08:30
  • @Jarod42 Oh, I didn't check the tags. The option is `-fno-elide-constructors` :) – eerorika Oct 27 '21 at 08:33
  • @Jarod42: OP tagged the question as C++11 because the example uses move mechanics which weren't available before, so the example is "C++11 and up". To tag the Q C++17, OP would have to be aware that something changed then that affected the example. And tagging the Q C++20 (as in "latest version") would needlessly restrict the audience, so... ;-) – DevSolar Oct 27 '21 at 08:36

1 Answers1

5

Why is that happening?

Since C++17: Because MyString("test") is a prvalue, and the language says that s1 is directly initialised from "test" in this case.

Before C++17: Because the side effects of move (and copy) constructor are not required/guaranteed to occur which allows the compiler to optimise by not creating the temporary object. This optimisation is called copy elision, and it's what your compiler did here.

How to use move constructor without std::move()?

You shouldn't want to use the move constructor. It's better to not create redundant temporary objects. What you've observed is better than what you were expecting.

In cases where you do need std::move (which doesn't apply here), you shouldn't want to avoid using it.

As such, the question is analogous to, "How do I make my pipes leak without using the appropriate tool?". 1. You don't want to make your pipes leak, and 2. If you for some reason do, just use the appropriate tool.

eerorika
  • 232,697
  • 12
  • 197
  • 326