在函数调用中访问和移动 unique_ptr

access and move unique_ptr in a function call

我有一个类似于下面的片段。

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

理论上应该可以。但是由于编译器(vs2015)没有严格遵循标准,所以func(ptr->some_data), std::move(ptr)的顺序是不确定的,即ptr可能在访问之前移动。

所以我的问题是如何使该段按预期工作?

完整代码如下:

#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) } {}
};

顺序确实未定义,但这并不重要,因为std::move实际上并没有从指针移动,它只是改变了值类别。

func(ptr->some_data)的调用将发生在指针移动之前,因为第一个是参数评估而后者发生在基类内部构造函数和参数评估总是在函数调用之前排序。

如果它让你感觉更好,你可以把它写成 100% 等价的:

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

编辑:如果参数按值传递,实际移动不会发生在被调用函数内部。但是谁用 unique_ptr 做这样的事情呢?

您可以使用构造函数链修复它:

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)) {}
};

原因:正如我在其他回答中所解释的,对 func 的调用肯定发生在委托构造函数调用之前,同时实际移动了 unique_ptr(而不是仅仅更改其值类别)绝对发生在里面。

当然,这依赖于另一个 C++11 特性,Visual C++ 可能正确也可能不正确。很高兴,delegating constructors are listed as supported since VS2013.


更好的做法是始终通过引用接受std::unique_ptr 参数,如果您打算窃取它们,则通过右值引用接受。 (如果你不会窃取内容,你为什么关心调用者有什么类型的智能指针?只接受一个原始的T*。)

如果你用过

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) } {}
};

你一开始就不会遇到这个问题,调用者的要求完全没有改变(必须提供一个右值,因为 unique_ptr 无论如何都是不可复制的)


将此作为通用规则的理由如下:按值传递允许复制或移动,以调用站点的最佳方式为准。但是 std::unique_ptr 是不可复制的,所以实际参数必须是右值。