C++ 如何编写安全的原子对象包装器?
我一直在尝试编写一个包装类来包装Win32内部函数,例如C++ 如何编写安全的原子对象包装器?,c++,C++,我一直在尝试编写一个包装类来包装Win32内部函数,例如InterlockedIncrement,InterlockedExchange。虽然我的问题可能与其他支持类似本质的平台类似 我有一个基本的模板类型: template <typename T, size_t W = sizeof(T)> class Interlocked {}; 模板 类互锁{}; 它部分专用于不同大小的数据类型。例如,下面是32位的: // // Partial specialization for
InterlockedIncrement
,InterlockedExchange
。虽然我的问题可能与其他支持类似本质的平台类似
我有一个基本的模板类型:
template <typename T, size_t W = sizeof(T)>
class Interlocked {};
模板
类互锁{};
它部分专用于不同大小的数据类型。例如,下面是32位的:
//
// Partial specialization for 32 bit types
//
template<typename T>
class Interlocked <T, sizeof(__int32)>
{
public:
Interlocked<T, sizeof(__int32)>() {};
Interlocked<T, sizeof(__int32)>(T val) : m_val(val) {}
Interlocked<T, sizeof(__int32)>& Interlocked<T, sizeof(__int32)>::operator= (T val)
{
InterlockedExchange((LONG volatile *)&m_val, (LONG)val);
return *this;
}
Interlocked<T, sizeof(__int32)> Interlocked<T, sizeof(__int32)>::operator++()
{
return static_cast<T>(InterlockedIncrement((LONG volatile *)&m_val));
}
Interlocked<T, sizeof(__int32)> Interlocked<T, sizeof(__int32)>::operator--()
{
return static_cast<T>(InterlockedDecrement((LONG volatile *)&m_val));
}
Interlocked<T, sizeof(__int32)>& Interlocked<T, sizeof(__int32)>::operator+(T val)
{
InterlockedExchangeAdd((LONG volatile *)&m_val, (LONG) val);
return *this;
}
Interlocked<T, sizeof(__int32)>& Interlocked<T, sizeof(__int32)>::operator-(T val)
{
InterlockedExchangeSubtract((LONG volatile *)&m_val, (LONG) val);
return *this;
}
operator T()
{
return m_val;
}
private:
T m_val;
};
//
//32位类型的部分专门化
//
模板
类联锁
{
公众:
互锁(){};
联锁(T val):m_val(val){}
联锁和联锁::运算符=(T val)
{
联锁交换((长波动*)和m_val,(长)val);
归还*这个;
}
联锁联锁::运算符++()
{
返回静态转换(联锁增量((长时间波动*)&m值));
}
联锁的:操作员--()
{
返回静态_cast(联锁减量((长volatile*)和m_val));
}
联锁和联锁::操作员+(T val)
{
InterlocatedExchangeAdd((长)volatile*)&m_val,(长)val);
归还*这个;
}
联锁和联锁::操作员-(T val)
{
联锁交换减法((长可变*)和m_val,(长)val);
归还*这个;
}
算子T()
{
返回m_val;
}
私人:
T m_val;
};
然而,我得出的结论是,我不知道如何安全地编写这样一个对象。具体地说,我已经意识到,在执行联锁操作后返回
*this
,允许另一个线程在返回变量之前更改变量。这将使类型的点为空。有可能写这样的东西吗?大概std::atomic解决了这个问题,但我在我的编译器中没有访问权限…如果没有std::atomic
,可以使用boost::atomic
(最新版本),这是经过良好测试的跨平台实现。如果没有std::atomic
,您可以使用boost::atomic
(最新版本),这是经过良好测试的跨平台实现。运算符+
和-
没有意义。您实际实现的更像是复合赋值(+=
,-=
),但您需要返回类型为T
的值,而不是对(*此)
的引用。当然,这不符合赋值运算符的约定std::atomic
选择使用命名函数,而不是运算符重载,除了++
和--
之外,可能是因为这个原因。运算符+
和-
没有意义。您实际实现的更像是复合赋值(+=
,-=
),但您需要返回类型为T
的值,而不是对(*此)
的引用。当然,这不符合赋值运算符的约定std::atomic
选择使用命名函数,而不是运算符重载,除了++
和--
,可能是因为这个原因。除了nogard的“使用其他人已经测试和工作过的实现”这一非常好的建议之外,我建议您不要返回*这个
,但是操作的结果——这就是现有互锁操作符的工作方式(以及std::atomic的工作方式)
换句话说,您的操作员代码应该如下所示:
T Interlocked<T, sizeof(__int32)>::operator+(T val)
{
return InterlockedExchangeAdd((LONG volatile *)&m_val, (LONG) val);
}
实际上可以做到:
b += c;
a = b;
除了nogard提出的“使用其他人已经测试和工作的实现”的非常好的建议外,我建议您不要返回
*this
,而是操作的结果-这是现有互锁操作符的工作方式(以及std::atomic的工作方式)
换句话说,您的操作员代码应该如下所示:
T Interlocked<T, sizeof(__int32)>::operator+(T val)
{
return InterlockedExchangeAdd((LONG volatile *)&m_val, (LONG) val);
}
实际上可以做到:
b += c;
a = b;
考虑两个线程在原子序数类上同时执行加法,其中线程#n将一个数量
t\u n
添加到您的数字x
您担心在一个线程中执行加法和返回结果之间,第二个线程可能会执行加法,从而弄乱第一个线程的返回值
该类用户的观察行为是返回值为(x+t\u 1+t\u 2)
,而不是预期的(x+t\u 1)
现在让我们假设您有一个不允许该行为的实现,即结果保证为(x#u 1+t_1)
,其中x_1
是线程1执行加法之前的数值
如果线程#2在线程#1之前立即执行并发加法,则得到的值为:
(x_1 + t_1) = ((x + t_2) + t_1)
这是完全相同的种族。除非在应用加法之前引入一些额外的同步或检查数字的预期值,否则您将始终获得此竞赛。考虑两个线程在原子数类上执行并发加法,其中线程#n将一个量
t#n
添加到您的数字x
您担心在一个线程中执行加法和返回结果之间,第二个线程可能会执行加法,从而弄乱第一个线程的返回值
该类用户的观察行为是返回值为(x+t\u 1+t\u 2)
,而不是预期的(x+t\u 1)
现在让我们假设您有一个不允许该行为的实现,即结果保证为(x#u 1+t_1)
,其中x_1
是线程1执行加法之前的数值
如果线程#2在