简体   繁体   中英

access and move unique_ptr in a function call

I have a segment similar to the following.

struct derive : base{
    derive(unique_ptr ptr): base{func(ptr->some_data), std::move(ptr)}{}
};

In theory, it should work. But since the compiler (vs2015) does not strictly follow the standard, the order of func(ptr->some_data), std::move(ptr) is undefined, ie ptr may be moved before accessed.

So my problem is how to make this segment work as expected?

Complete code like this:

#include <memory>

struct base {
    virtual ~base() = 0 {}

protected:
    base(std::unique_ptr<base> new_state) :
        previous_state{ std::move(new_state) } {}
private:
    std::unique_ptr<base> previous_state;
};

struct derive_base : base {
    int get_a() const noexcept {
        return a;
    }
protected:
    derive_base(int const new_a, std::unique_ptr<base> new_state) :
        base{ std::move(new_state) }, a{ new_a } {}
private:
    int a;
};

struct final_state : derive_base {
    final_state(std::unique_ptr<base> new_state) :
        derive_base{ dynamic_cast<derive_base&>(*new_state).get_a(), std::move(new_state) } {}
};

You can fix it using constructor chaining:

struct derive : base
{
  private:
    derive(const D& some_data, unique_ptr<X>&& ptr) : base{some_data, std::move(ptr)} {}
  public:
    derive(unique_ptr<X> ptr): derive(func(ptr->some_data), std::move(ptr)) {}
};

Reason: As explained in my other answer, the call to func definitely takes place before the delegated constructor call, while actually moving the unique_ptr (as opposed to merely changing its value category) definitely takes place inside.

Of course, this relies on another C++11 feature which Visual C++ may or may not have gotten right. Happily, delegating constructors are listed as supported since VS2013 .


An even better thing to do is just always accept std::unique_ptr arguments by reference, and by rvalue reference if you plan to steal from them . (And if you won't steal the content, why do you care what type of smart pointer the caller has? Just accept a raw T* .)

If you used

struct base
{
    virtual ~base() = 0 {}

protected:
    base(std::unique_ptr<base>&& new_state) :
        previous_state{ std::move(new_state) } {}
private:
    std::unique_ptr<base> previous_state;
};

struct derive_base : base
{
    int get_a() const noexcept {
        return a;
    }
protected:
    derive_base(int const new_a, std::unique_ptr<base>&& new_state) :
        base{ std::move(new_state) }, a{ new_a } {}
private:
    int a;
};

struct final_state : derive_base
{
    final_state(std::unique_ptr<base>&& new_state) :
        derive_base{ dynamic_cast<derive_base&>(*new_state).get_a(), std::move(new_state) } {}
};

you wouldn't have had the problem in the first place, and the caller requirements are completely unchanged (an rvalue must be provided, since unique_ptr is uncopyable anyway)


The rationale for making this a universal rule is as follows: pass by value allows either copying or moving, whichever is more optimal at the call site. But std::unique_ptr is non-copyable, so the actual parameter MUST be an rvalue anyway.

The order is indeed undefined, but that doesn't matter because std::move doesn't actually move from the pointer, it only changes the value category.

The call to func(ptr->some_data) will take place before the pointer is moved, because the first is argument evaluation and the latter happens inside the base constructor, and argument evaluation always is ordered before a function call.

If it makes you feel better, you can write it as the 100% equivalent:

 
 
 
  
  derive(unique_ptr<X> ptr): base{func(ptr->some_data), (unique_ptr<X>&&)ptr}{}
 
  

Edit: the actual move doesn't take place inside the called function, if the parameter is pass by-value. But who does such a thing with unique_ptr s?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM