2

I often see the following idiom in production code: A value argument (like a shared pointer) is handed into a constructor and shall be copied once. To ensure this, the argument is wrapped into a std::move application. Hating boilerplate code and formal noise I wondered if this is actually necessary. If I remove the application, at least gcc 7 outputs some different assembly.

#include <memory> class A { std::shared_ptr<int> p; public: A(std::shared_ptr<int> p) : p(std::move(p)) {} //here replace with p(p) int get() { return *p; } }; int f() { auto p = std::make_shared<int>(42); A a(p); return a.get(); } 

Compiler Explorer shows you the difference. While I am not certain what is the most efficient approach here, I wonder if there is an optimization that allows to treat p as a rvalue reference in that particular location? It certainly is a named entity, but that entity is "dead" after that location anyway.

Is it valid to treat a "dead" variable as a rvalue reference? If not, why?

4
  • What if you want to use it more than once (it's more likely than you think)? You'd have to signal it shouldn't be consumed instead. Opt-in is how most things in C++ work. Commented Apr 9, 2018 at 7:01
  • The thing is, I do not use it after the initialization (and not even before, too). The compiler should be able to see that with a basic liveness analysis. The question is: Why is it not allowed to treat the variable as a rvalue reference here. Commented Apr 9, 2018 at 7:49
  • shared_ptr is a special case here ... its copy operation is to make another pointer to the same managed object. Are you intending specifically to ask about shared_ptr? This differs from most objects which do a complete copy of their state when copied. Commented Apr 9, 2018 at 8:04
  • There isn't any "treating as rvalue reference" in this code, could you indicate more clearly what you are talking about? Commented Apr 9, 2018 at 8:05

1 Answer 1

2

In the body of the constructor, there are two p objects, the ctor argument and this->p. Without the std::move, they're identical. That of course means the ownership is shared between the two pointers. This must be achieved in a thread-safe way, and is expensive.

But optimizing this out is quite hard. A compiler can't generally deduce that ownership is redundant. By writing std::move yourself, you make it unambiguously clear that the ctor argument p does not need to retain ownership.

Sign up to request clarification or add additional context in comments.

2 Comments

Are you saying that the naming is the argument here (member and constructor argument having the same name)? If I am not mistaken, std::move tells the compiler that its argument can be treated like a temporary. But shouldn't it be easy to tell that from the fact that the name of the variable does not occur after that point?
@choeger: No, names are irrelevant here. The important fact is that they would hold the same pointer value, if not for the std::move. And yes, it's easy here to tell that the function argument p is not used here. That's far less common in real code, which is more complex. As soon as the compiler sees p->Foo, which really is p.operator->, you'd need flow analysis to determine whether how big the effective scope is.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.