现在我们已经了解了所有基本的原子类型,它们都是std::atomic<>主类模板实例化出来的,而不是模板特化。下面我们看看std::atomic<>类模板。
5.2.6 std::atomic<>主模板
std::atomic<>类模板允许用户定义自己的原子类型。可以使用任意自定义类型作为模板参数,但是这个类型必须遵守几个规则。这个类型必须提供平凡(trivial)的拷贝赋值运算符函数,意味着不能拥有虚函数以及虚基类,而且必须使用编译器自动合成的拷贝赋值运算符。而且,这个类的所有基类以及非静态数据成员都需要提供这样的拷贝赋值运算符。这在本质上意味着允许编译器在赋值时使用memcpy()或者等价的操作,因为不存在用户定义的代码。
最后,这个类型还需要具有按位可比性(bitwise equality comparable)。这个要求是与赋值操作并驾齐驱的,它不只要求可以使用memcpy() 进行拷贝,还要能够使用memcmp()进行比较。这些要求最终的目的是为了能够正确的执行“比较/交换”(compare/exchange)操作。
这些限制都来自第3章所述的一个原则:不要把指向被保护数据的指针或者引用从具有锁定保护的代码范围中传出去,作为用户指定函数的参数。通常情况下,编译器不能为用户定义的std::atomic
注意,尽管你可以使用std::atomic
对于std::atomic
这些限制意味着你不能定义一个类似std::atomic
一旦你定义了一个std::atomic
5.2.7 与原子类型的相关全局函数
除了之前说的成员函数外,还有一些具有等价功能的全局函数。大多数全局函数的名字与对应的成员函数一样,只是前面加了一个前缀:atomic_,例如:atomic_load()。这些函数对所有的原子类型都做了重载。为了有机会指定内存指令,每种函数都有两个版本:不带后缀的版本不指定内存指令,例如:
std::atomic_store(&atomic_var,new_value);
带有 _explicit后缀的版本可以在后面指定一个或多个内存指令,例如:
store_explicit(&atomic_var,new_value,std::memory_order_release);
成员函数直接调用,全局函数则是将一个原子类型的指针作为第一个参数传入。
这些全局函数是为了兼容C语言而定义的,所以使用指针而不是引用。与std::atomic_flag相关的函数是个特例,名字中包含有“flag”字样,例如:std::atomic_flag_test_and_set()、std::atomic_flag_clear() , std::atomic_flag_test_and_set_explicit()、 std::atomic_
flag_clear_explicit()。
C++标准库也为std::shared_ptr<>实例提供了相应的全局函数。这打破了只有原子类型才有原子操作这一常规,因为std::shared_ptr<>显然不是一个原子类型。尽管这样,C++委员会依然认为提供这个额外的还是非常有必要的。有效的原子操作有:load,store,exchange,compare/exchange,它们以标准原子类型的相关操作的重载函数的形式提供,接受一个std::shared_ptr<>*作为第一个参数:
std::shared_ptr p;
void process_global_data()
{
std::shared_ptr local = std::atomic_load(&p);
process_data(local);
}
void update_global_data()
{
std::shared_ptr local(new my_data);
std::atomic_store(&p, local);
}