问题
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, i.e. 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) } {}
};
回答1:
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.
回答2:
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_ptrs?
来源:https://stackoverflow.com/questions/28336207/access-and-move-unique-ptr-in-a-function-call