Переместить семантику и оценку порядка функций

Предположим, у меня есть следующее:

#include <memory>
struct A { int x; };

class B {
B(int x, std::unique_ptr<A> a);
};

class C : public B {
C(std::unique_ptr<A> a) : B(a->x, std::move(a)) {}
};

Если я правильно понимаю правила C ++ относительно «неопределенного порядка параметров функций», этот код небезопасен. Если второй аргумент Bконструктор сначала создается с помощью конструктора перемещения, затем a теперь содержит nullptr и выражение a->x вызовет неопределенное поведение (вероятно, segfault). Если первый аргумент создается первым, то все будет работать так, как задумано.

Если бы это был обычный вызов функции, мы могли бы просто создать временный:

auto x = a->x
B b{x, std::move(a)};

Но в списке инициализации класса у нас нет свободы для создания временных переменных.

Предположим, я не могу изменить BЕсть ли какой-нибудь возможный способ для достижения вышеизложенного? А именно разыменование и перемещение unique_ptr в том же выражении вызова функции без создания временного?

Что делать, если вы могли бы изменить Bконструктор, но не добавлять новые методы, такие как setX(int)? Это поможет?

Спасибо

47

Решение

Используйте инициализацию списка для создания B, Элементы гарантированно будут оцениваться слева направо.

C(std::unique_ptr<A> a) : B{a->x, std::move(a)} {}
//                         ^                  ^ - braces

От §8.5.4 / 4 [dcl.init.list]

В пределах инициализатора-лист из приготовился-INIT-лист, инициализатора-статьи, включая любые, которые являются результатом расширений пакета (14.5.3), оцениваются в порядке, в котором они появляются. То есть каждое значение вычисления и побочный эффект связаны с данным инициализатор-раздел последовательность перед каждым вычислением значения и побочным эффектом, связанным с любым инициализатор-раздел который следует за ним в списке через запятую инициализатора-лист.

46

Другие решения

В качестве альтернативы ответу Преториана вы можете использовать делегат конструктора:

class C : public B {
public:
C(std::unique_ptr<A> a) :
C(a->x, std::move(a)) // this move doesn't nullify a.
{}

private:
C(int x, std::unique_ptr<A>&& a) :
B(x, std::move(a)) // this one does, but we already have copied x
{}
};
30

Предложение Преториана об использовании инициализации списка, кажется, работает, но у него есть несколько проблем:

  1. Если аргумент unique_ptr на первом месте, нам не повезло
  2. Это слишком просто для клиентов B случайно забыть использовать {} вместо (), Дизайнеры BИнтерфейс наложил эту потенциальную ошибку на нас.

Если бы мы могли изменить B, то, возможно, лучшее решение для конструкторов — всегда передавать unique_ptr по ссылке rvalue, а не по значению.

struct A { int x; };

class B {
B(std::unique_ptr<A>&& a, int x) : _x(x), _a(std::move(a)) {}
};

Теперь мы можем безопасно использовать std :: move ().

B b(std::move(a), a->x);
B b{std::move(a), a->x};
9
По вопросам рекламы [email protected]