choeger
choeger

Reputation: 3567

std::move in initializer lists

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?

Upvotes: 2

Views: 460

Answers (1)

MSalters
MSalters

Reputation: 180295

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.

Upvotes: 2

Related Questions