C++11: написать конструктор перемещения с элементом atomic‹bool›?

У меня есть класс с атомарной переменной-членом:

struct Foo
{
  std::atomic<bool> bar;
  /* ... lots of other stuff, not relevant here ... */
  Foo() 
  : bar( false )
  {}

  /* Trivial implementation fails in gcc 4.7 with:
   *   error: use of deleted function ‘std::atomic<bool>::atomic(const td::atomic<bool>&)’
   */
  Foo( Foo&& other )
  : bar( other.bar )
  {}
};

Foo f;
Foo f2(std::move(f));  // use the move

Как должен выглядеть конструктор перемещения?

Gcc 4.7 не нравится ни одна из моих попыток (например, добавление std::move() вокруг other.bar), и сеть здесь на удивление тихая...


person Chris    schedule 06.01.2013    source источник


Ответы (3)


Поскольку вы перемещаете other, никто другой не получит к нему доступ. Так что чтение из его bar безопасно, независимо от того, атомарное оно или нет.

atomic<T> имеет только два конструктора, один по умолчанию (), а другой (T). Итак, ваш код выглядит так, как будто он должен скомпилироваться. Если это не так, что произойдет, если вы примените static_cast other.bar к T, заставив использовать конструктор (T)?

: bar( static_cast< bool >( other.bar ) )

или что равнозначно и, возможно, менее уродливо:

: bar( other.bar.load( ) )

person gustaf r    schedule 06.01.2013
comment
Спасибо, bar( other.bar.load() ) — правильное решение, которое сейчас компилируется! - person Chris; 06.01.2013
comment
Итак, ваш код выглядит так, как будто он должен компилироваться. Нет, atomic<T> имеет удаленный конструктор копирования, и разрешение перегрузки обнаруживает, что это не конструктор atomic(T). Необходим бросок или нагрузка. - person Jonathan Wakely; 06.01.2013
comment
@JonathanWakely, вы правы, я думал, что компилятор перейдет к следующему возможному конструктору (T), когда он узнает, что конструктор копирования удален и есть operator T. Является ли причиной этого то, что атомарным конструктором для (T) является constexpr, заставляющий его вести себя аналогично конструкторам explicit в том смысле, что неявные преобразования должны быть либо явными (или, в данном случае, constexpr)? - person gustaf r; 06.01.2013
comment
Нет, причина в том, что лучшим соответствием для вызова является конструктор копирования, поэтому разрешение перегрузки выбирает именно его. Удаленная функция не означает игнорирование этой функции, это означает, что если эта функция будет вызвана, ваша программа не сможет скомпилироваться - person Jonathan Wakely; 06.01.2013
comment
Это не работает, когда у Foo есть другие переменные-члены. Придется кропотливо перемещать каждый из них в определяемом пользователем конструкторе перемещения. Я сталкивался с этим несколько раз и поэтому решил избегать атомарности, когда это возможно, и вместо этого использовать мьютексы. - person John Jiang; 21.11.2019

std::atomic нельзя копировать или перемещать, поскольку его конструктор копирования удален, а конструктор перемещения не определен. Вы должны явно загрузить другое значение и использовать его для построения нового значения, как было указано в ответе Густава.

Почему std::atomic не подвижен? Поскольку это примитив синхронизации, все потоки должны синхронизироваться с одними и теми же данными (т. е. с одним и тем же адресом). Когда вы копируете (или перемещаете) атомарное значение, вы должны использовать какой-то протокол связи. Это может быть просто, как в вашем примере (просто загрузите его и используйте для инициализации нового атома), но в целом я думаю, что это хорошее дизайнерское решение C++11, чтобы заставить вас подумать об этом. В противном случае это может привести к тому, что код выглядит нормально, но имеет некоторые проблемы с синхронизацией тайлов.

person Philipp Claßen    schedule 06.01.2013
comment
Я пытаюсь выяснить ситуацию, когда на самом деле может быть проблематично автоматически перемещать атом, когда у вас есть несколько потоков. Другое дело принудительное перемещение вручную с помощью std::move(). В любом случае, мне нужен пример, показывающий, где перемещение атома может быть проблематичным. Вы можете перемещать любой другой std контейнер, и вы должны позаботиться о синхронизации. - person gustaf r; 06.01.2013
comment
Я также хотел бы знать, было бы плохой идеей, если бы atomic имел конструктор перемещения, который нужно было бы запускать явным вызовом std::move() - это дало бы мне интерфейс, который я ожидал. Но я слишком далек от того, чтобы быть экспертом по С++, чтобы судить, имеет ли это решение о стандарте цель или является ошибкой... - person Chris; 06.01.2013
comment
Конструктор перемещения не может решить, является ли rvalue результатом явного std::move или неявным rvalue (например, возвращаемым значением функции). Я также не эксперт в проектных решениях, но я думаю, что ресурс с подсчетом ссылок является примером, когда конструктор перемещения может привести к проблемам. Я отредактирую свой ответ и включу пример, который я имел в виду, когда писал о проблемах с синхронизацией. - person Philipp Claßen; 07.01.2013
comment
Я снова подумал об этом, но, честно говоря, я не мог придумать ни одного примера, где вы хотите переместить атом. Даже пример с подсчетом ссылок ошибочен, потому что нет необходимости делать что-то вроде перемещения. Так что я прихожу к выводу, что вы никогда не хотите логически его перемещать, хотя иногда вам хочется его скопировать. Копирование, однако, сложнее, чем вы думаете. Вы должны загрузить значение (что может включать синхронизацию кеша), а затем вы должны назначить его (т. е. другую возможную синхронизацию). Сказав это, я думаю, что подход с явным требованием load в порядке. - person Philipp Claßen; 07.01.2013

Экземпляр шаблона atomic<bool> по существу выглядит следующим образом:

struct atomic<bool>
{
    atomic<bool>(bool);
    atomic<bool>( const atomic<bool>& ) = delete;
    operator bool() const;
}

Итак, когда вы пытаетесь скопировать его:

atomic<bool> a = ...;
atomic<bool> b(a);

Выбирается удаленный конструктор копирования, что вызывает ошибку компиляции.

Вам нужно явно указать тип bool, чтобы пройти через operator bool() --> atomic<bool>(bool)...

atomic<bool> a = ...;
atomic<bool> b(bool(a));
person Andrew Tomazos    schedule 06.01.2013
comment
Не безопаснее ли использовать static_cast‹bool› (имея в виду последнюю строку кода в вашем решении)? - person Guy Avraham; 04.06.2017
comment
@GuyAvraham, поскольку новые именованные приведения предпочтительнее из соображений ясности по сравнению со старым синтаксисом. В этом случае .load() принесет наибольшую ясность. - person Xeverous; 26.11.2020