2

The following is toy code I am trying... I understand the first and second one. The first one give the ownership to _p. The second one copies p to _p. but I don't understand the third one...

What does std::move of const shared_ptr & mean? Thank you.

class P { };

class A {
public:
    // first one
    A(std::shared_ptr<P> &p, int) : _p(std::move(p))
    {
        std::cout << "1st Ctor: "
                  << p.use_count() << ", " << _p.use_count() << std::endl;
    }

    // second one
    A(const std::shared_ptr<P> &p, std::string) : _p(p)
    {
        std::cout << "2nd Ctor: "
                  << p.use_count() << ", " << _p.use_count() << std::endl;
    }

    // third one
    A(const std::shared_ptr<P> &p) : _p(std::move(p))
    {
        std::cout << "3rd Ctor: "
                  << p.use_count() << ", " << _p.use_count() << std::endl;
    }

private:
    std::shared_ptr<P> _p;
};

int main()
{
    {
        std::shared_ptr<P> p = std::make_shared<P>();
        A a(p, 1);
        std::cout << "1. body: " << p.use_count() << std::endl;
    }
    std::cout << "-------------" << std::endl;
    {
        std::shared_ptr<P> p = std::make_shared<P>();
        A a(p, "2");
        std::cout << "2. body: " << p.use_count() << std::endl;
    }
    std::cout << "-------------" << std::endl;
    {
        std::shared_ptr<P> p = std::make_shared<P>();
        A a(p);
        std::cout << "3. body: " << p.use_count() << std::endl;
    }
 }

Result is:

$ ./a.out 
1st Ctor: 0, 1
1. body: 0
-------------
2nd Ctor: 2, 2
2. body: 2
-------------
3rd Ctor: 2, 2
3. body: 2

(updated: adding comment to clarify which one is first one, second one, etc.)

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
eric
  • 63
  • 7
  • https://stackoverflow.com/questions/10770181/should-a-move-constructor-take-a-const-or-non-const-rvalue-reference – Big Temp May 15 '20 at 10:49
  • You probably should be more clear on what you mean by "the first one" (the first line, the first block, the first call to `std::move`?). The same applies to your question: what do you mean by "`std::move` **of** `const shared_ptr &`"? Cite specific lines. – Albert May 15 '20 at 10:54
  • On my team, 1st, 2nd and 3rd would all be dinged as bad practice in a code review. For transferring ownership `A(std::shared_ptr

    && p)` (which is a wee bit odd for a shared_ptr), or `A(std::shared_ptr

    p)` (typical pattern for any sink parameter) would be the way to express the intent.

    – Eljay May 15 '20 at 12:35
  • @Eljay, Thanks for the comment. what you suggested is clearer to use. With your comment, I found that `const shared_ptr &` seems somewhat ambiguous in terms of copy/move. – eric May 18 '20 at 02:58
  • @eric `const shared_ptr &` is unambiguously not for moving. – eerorika May 18 '20 at 03:27

2 Answers2

5

std::move just performs conversion and produces xvalue (rvalue).

When being passed a const std::shared_ptr<P>, its return type would be const std::shared_ptr<P>&&. Then for _p(std::move(p)) the copy constructor of std::shared_ptr (but not move constructor which taking rvalue-reference to non-const) will be called, the effect is just same as the 2nd case.

Basically move operation tends to perform modification on the object being moved; it's not supposed to work on const objects.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • Thanks for the comment. I tried to inherit `std::shared_ptr` and printed at copy ctor and move ctor. As you answered, when the param was `const shared_ptr &`, copy ctor was called!! – eric May 18 '20 at 02:56
0

std::move is a function which converts the argument to an rvalue reference. The function call is an xvalue expression.

When the argument is a reference to const, then the result of the conversion is an rvalue to const. If you initialise from rvalue to const, copy constructor will be used because the rvalue reference parameter to non-const of the move constructor cannot bind to rvalue reference argument to const.

I think there is also an implicit question by OP of how _p(std::move(p)) might differ from _p(p)

_p(std::move(p)) doesn't differ from _p(p) in case of const std::shared_ptr<T>.

In theory, if decltype(_p) was a type that had a constructor T(const T&&), then there would be a difference, since that constructor would be invoked by _p(std::move(p)) but not by _p(p). Such constructor would be quite unconventional, but technically well-formed. std::shared_ptr doesn't have such constructor.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • I think there is also an implicit question by OP of how `_p(std::move(p))` might differ from `_p(p)` – M.M May 18 '20 at 02:41
  • @M.M I had considered that there was no need to mention that `_p(p)` also does a copy. I've added clarification. – eerorika May 18 '20 at 02:56
  • OK. I guess it would differ for a class that had separate `const T&&` and `T&&` constructors – M.M May 18 '20 at 02:58
  • @M.M Indeed it would. Have you seen `const T&&` constructor used by the way? I've seen it a few times with non-constructor functions, but never in a constructor. – eerorika May 18 '20 at 03:07