Доступ и перемещение unique_ptr в вызове функции

У меня есть сегмент, похожий на следующий.

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

В теории, он должен работать. Но поскольку компилятор (vs2015) строго не соответствует стандарту, порядок func(ptr->some_data), std::move(ptr) равен undefined, т.е. 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) } {}
};

Ответ 1

Вы можете исправить это, используя цепочку конструкторов:

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 (а не просто изменение его категории значений) определенно происходит внутри.

Конечно, это зависит от другой возможности С++ 11, которую Visual С++ может или не может получить. К счастью, делегирующие конструкторы перечислены как поддерживаемые с VS2013.


Еще лучше сделать только всегда принимать аргументы std::unique_ptr по ссылке и по ссылке rvalue, если вы планируете украсть у них. (И если вы не будете красть контент, почему вам небезразличен тип смарт-указателя, который имеет вызывающий абонент? Просто примите raw 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) } {}
};

у вас не было бы проблемы в первую очередь, и требования к вызывающему абоненту полностью не изменились (должно быть предоставлено rvalue, так как unique_ptr в любом случае не подлежит сомнению)


Обоснование принятия этого универсального правила заключается в следующем: pass by value позволяет либо копировать, либо перемещаться, в зависимости от того, что более оптимально на сайте вызова. Но std::unique_ptr не скопируется, поэтому фактический параметр ДОЛЖЕН быть rvalue в любом случае.

Ответ 2

Порядок действительно undefined, но это не имеет значения, поскольку std::move фактически не перемещается из указателя, он только меняет категорию значений.

Вызов func(ptr->some_data) будет до перемещения указателя, потому что первая - это оценка аргумента, а последняя - внутри базового конструктора, а оценка аргументов всегда упорядочена до вызов функции.

Если вам становится лучше, вы можете записать его как 100% -ный эквивалент:

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

Забастовкa >

Изменить: фактическое перемещение не происходит внутри вызываемой функции, если параметр передается по значению. Но кто делает такую ​​вещь с unique_ptr s?