С++0x | Почему std::atomic перегружает каждый метод классификатором volatile?

Следующий отрывок из текущего проекта показывает, что я имею в виду:

namespace std {
    typedef struct atomic_bool {
        bool is_lock_free() const volatile;
        bool is_lock_free() const;
        void store(bool, memory_order = memory_order_seq_cst) volatile;
        void store(bool, memory_order = memory_order_seq_cst);
        bool load(memory_order = memory_order_seq_cst) const volatile;
        bool load(memory_order = memory_order_seq_cst) const;
        operator bool() const volatile;
        operator bool() const;
        bool exchange(bool, memory_order = memory_order_seq_cst) volatile;
        bool exchange(bool, memory_order = memory_order_seq_cst);
        bool compare_exchange_weak(bool&, bool, memory_order, memory_order) volatile;
        bool compare_exchange_weak(bool&, bool, memory_order, memory_order);
        bool compare_exchange_strong(bool&, bool, memory_order, memory_order) volatile;
        bool compare_exchange_strong(bool&, bool, memory_order, memory_order);
        bool compare_exchange_weak(bool&, bool, memory_order = memory_order_seq_cst) volatile;
        bool compare_exchange_weak(bool&, bool, memory_order = memory_order_seq_cst);
        bool compare_exchange_strong(bool&, bool, memory_order = memory_order_seq_cst) volatile;
        bool compare_exchange_strong(bool&, bool, memory_order = memory_order_seq_cst);
        atomic_bool() = default;
        constexpr atomic_bool(bool);
        atomic_bool(const atomic_bool&) = delete;
        atomic_bool& operator=(const atomic_bool&) = delete;
        atomic_bool& operator=(const atomic_bool&) volatile = delete;
        bool operator=(bool) volatile;
    } atomic_bool;
}

Летучий является транзитивным. Таким образом, вы не можете вызывать энергонезависимую функцию-член из изменчивого объекта. С другой стороны, вызов изменяемой функции-члена из энергонезависимого объекта разрешен.

Итак, есть ли разница в реализации между volatile и энергонезависимыми функциями-членами в атомарных классах? Другими словами, есть ли необходимость в энергонезависимой перегрузке?


person 0xbadf00d    schedule 02.02.2011    source источник
comment
Лучше спросить, зачем вообще нужны volatile перегрузки.   -  person GManNickG    schedule 02.02.2011
comment
@GMan: потому что иначе функции нельзя было бы вызывать для изменчивых данных. ;)   -  person jalf    schedule 02.02.2011
comment
@jalf: Ха, да, но поскольку операции, которые выполняет сам тип, являются атомарными (и, следовательно, наблюдаемыми), зачем нам делать volatile atomic<>? Я думаю, что упускаю что-то важное.   -  person GManNickG    schedule 02.02.2011
comment
@GMan: а почему нельзя? Предположим, у вас есть atomic<> в качестве члена другой структуры, и создается экземпляр volatile? Тогда все его члены тоже будут неявно volatile, и без перегрузок volatile ваш новый блестящий класс atomic будет бесполезен. :)   -  person jalf    schedule 02.02.2011
comment
О, и если копнуть немного дальше, в 29.6/3 есть примечание по этому поводу: многие операции изменчивы. Семантика volatile как регистра устройства не изменилась в стандарте. Эта квалификация означает, что волатильность сохраняется при применении этих операций к волатильным объектам.   -  person jalf    schedule 02.02.2011
comment
@jalf: это было бы запрещено, потому что это было бы избыточным дополнительным кодом. Ваши те же рассуждения означают, что std::string тоже должен иметь все свои функции-члены volatile, не так ли?   -  person GManNickG    schedule 02.02.2011
comment
@GMan: нет, потому что string не предназначен для использования в изменчивом режиме (он не является потокобезопасным, поэтому он не сможет обработать, если базовая память будет внезапно изменена драйвером или чем-то подобным). Но это была бы довольно плохая атомарная переменная, если бы ее нельзя было рассматривать как изменчивую. ;) Помните, что цель volatile состоит в том, чтобы указать, что объект может быть изменен без ведома программы. Это не имеет смысла для строки, но это то, что объект, предназначенный как атомарный, должен иметь возможность обрабатывать.   -  person jalf    schedule 02.02.2011
comment
@jalf: Но предполагается, что атом всегда может быть изменен без ведома программы, это по своей природе volatile. Маркировка volatile не имеет семантической разницы, в отличие от других переменных volatile.   -  person GManNickG    schedule 03.02.2011
comment
@GMan: точно. Если объект ведет себя как изменчивый, то он также должен работать, если я добавлю квалификатор volatile, не так ли? Не могли бы вы также возразить, что нет смысла отмечать членов класса const, если класс в любом случае семантически постоянен? Это не имеет никакого значения, кроме того, что позволяет классу продолжать работать в контексте, где присутствует соответствующий квалификатор   -  person jalf    schedule 03.02.2011
comment
@jalf: Если объект ведет себя так, как будто он изменчив, то он также должен работать, если я добавлю квалификатор volatile, не так ли? Возможно, но мой вопрос в том, почему нам нужно даже поддерживать это, если в любом случае добавление ключевого слова they не имеет значения. Ваш пример const отлично подходит для демонстрации того, почему нам нужно const в функции-члене, но что, если бы у нас был тип, который по своей сути был бы константным? (Как, скажем, std::integral_constant.) Что хорошего в создании экземпляра этого const?   -  person GManNickG    schedule 04.02.2011
comment
@GMan и jalf +1: Да, это хороший момент.   -  person 0xbadf00d    schedule 04.02.2011
comment
@GMan: например, то, что я уже сказал: это может быть константа просто потому, что она является членом объекта, который вы создаете как константу. я хочу сказать, что мы должны признать, что эти квалификаторы всегда могут быть добавлены к объекту, и это не должно нарушать тип.   -  person jalf    schedule 05.02.2011
comment
Или у вас может быть шаблон функции, который выполняет какую-то операцию с неизвестным volatile объектом. Поэтому он принимает параметр типа volatile T. Теперь вы, очевидно, хотите иметь возможность передать atomic<int> этой функции, потому что предполагается, что она работает с объектами, которые являются изменчивыми. И, черт возьми, ваш код не скомпилируется, потому что ваш семантически изменчивый объект больше не может использоваться, когда к нему добавляется квалификатор volatile. Это было бы абсурдно, точно так же, как гипотетический std::integral_constant должен иметь возможность передаваться функции, которая ожидает const T без нарушения.   -  person jalf    schedule 05.02.2011
comment
@jalf: я думаю, что понимаю, откуда вы пришли, поэтому я предварительно соглашусь, что нам нужны перегрузки. Но я все еще думаю, что это странно для кого-то когда-либо иметь volatile atomic<>.   -  person GManNickG    schedule 09.02.2011


Ответы (2)


Я думаю, что летучие перегрузки существуют из соображений эффективности. Неизменяемые операции чтения и записи по своей природе дороже, чем энергонезависимые операции чтения и записи в C++0x, поскольку модель памяти предъявляет некоторые жесткие требования, препятствующие кэшированию значений энергозависимых переменных. Если бы все функции были помечены только как volatile, тогда код не обязательно мог бы выполнять определенные оптимизации, которые в противном случае повысили бы производительность. Наличие различия позволяет компилятору оптимизировать энергонезависимое чтение и запись, когда это возможно, и изящно снижать производительность, когда требуется энергозависимое чтение и запись.

person templatetypedef    schedule 02.02.2011
comment
Квалификатор volatile просто предотвращает оптимизацию компилятора. Кроме того, насколько мне известно, квалификатор volatile, применяемый к функциям-членам, позволяет вызывать этот метод только из volatile-объектов и не влияет на результирующий код. - person 0xbadf00d; 02.02.2011
comment
@ FrEEzE2046- В C++0x определение volatile указано гораздо более жестко и на самом деле означает больше, чем просто не оптимизировать. Кроме того, более точное значение модификатора volatile в функции-члене заключается в том, что указатель this равен volatile, и поэтому любой доступ к переменным-членам, который происходит в функции, неявно станет volatile. - person templatetypedef; 02.02.2011
comment
Хорошо, это означает, что любой доступ к объекту при вызове изменчивой функции-члена не будет оптимизирован? (Описание изменчивого: Доступ к изменчивым объектам оценивается строго в соответствии с правилами абстрактной машины.) - person 0xbadf00d; 02.02.2011
comment
+1 Добавлять ли энергонезависимые версии было одним из открытых вопросов в более ранних черновиках. Он был добавлен специально по просьбам разработчиков компилятора. См. open-std.org/jtc1. /sc22/wg21/docs/papers/2009/n2925.html#LWG1147. - person stephan; 03.03.2011

Во-первых, кажется излишним создавать volatile std::atomic. На самом деле, я могу представить полезную ситуацию. Предполагая, что у нас есть фиксированный адрес устройства (памяти), с которым мы хотим работать. В связи с тем, что классы std::atomic_xxx, а также размеры классов шаблонов std::atomic‹> должны быть того же размера, что и соответствующие им встроенные типы, вы можете захотеть обрабатывать оба: выполнение атомарных операций с контролем над порядок памяти и убедитесь, что доступ к нашему атомарному объекту никогда не оптимизируется. Таким образом, мы могли бы объявить что-то вроде:

std::atomic<long> volatile* vmem_first4 = reinterpret_cast<std::atomic<long> volatile*>(0xxB8000);
person 0xbadf00d    schedule 03.02.2011
comment
Стандарт прямо говорит, что atomic<T> может не иметь того же размера, что и эквивалентный T. - person jalf; 03.02.2011
comment
Представление атомарного типа адреса не обязательно должно иметь тот же размер, что и соответствующий ему обычный тип. По возможности он должен иметь одинаковый размер. Так что летучий атом «Т» кажется мне бесполезным. - person 0xbadf00d; 03.02.2011