Получить доступ и переместить 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) не определено, т.е. 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) } {}
};

2 ответа

Решение

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

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++ может или не может получить право. К счастью, делегирующие конструкторы перечислены как поддерживаемые начиная с VS2013.


Еще лучше, просто всегда принимать std::unique_ptr аргументы по ссылке и по rvalue ссылке, если вы планируете украсть у них. (И если вы не будете красть контент, почему вас волнует, какой тип умного указателя у вызывающего абонента? Просто примите необработанный 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 не подлежит копированию, поэтому фактический параметр ДОЛЖЕН быть в любом случае r-значением.

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

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

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

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

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

Другие вопросы по тегам