我写了一个类,其中几个线程可以访问实例。我用一个技巧来记住用户在使用之前必须锁定对象。它涉及只保留const实例。当需要读取或修改敏感数据时,其他类应该调用一个方法(这是const,因此允许)来获取锁定对象的非const版本。实际上它返回一个包含指向非const对象和scoped_lock的指针的代理对象,因此它在超出范围时解锁对象。代理对象也会重载operator->所以对对象的访问是透明的。
这样,通过访问未锁定的对象来拍摄自己的脚就更难了(总是有const_cast)。
应该避免“巧妙的伎俩”,无论如何这都闻起来很糟糕。
这个设计真的很糟糕吗? 我还能做什么?
编辑:Getters是非const来强制执行锁定。
答案 0 :(得分:3)
基本问题:其他地方可能存在非const引用。如果安全地写,那么它就不能安全地读取 - 你可能会看到一个中间状态。
此外,一些const方法可能(合法地)以线程不安全的方式修改隐藏的内部细节。
分析您对该对象的实际操作以及find an appropriate synchronisation mode。
如果你的聪明容器确实对对象有足够的了解,可以通过代理控制所有同步,那么就将这些对象设为私有内部类。
答案 1 :(得分:2)
这很聪明,但不幸的是注定要失败。
spraff
强调的问题是,您可以防止读取而不是写入。
考虑以下顺序:
unsigned getAverageSalary(Employee const& e) {
return e.paid() / e.hired_duration();
}
如果我们在两个函数调用之间增加paid
会发生什么?我们得到了一个不连贯的价值。
问题是您的方案没有明确强制执行锁定读取。
考虑代理模式的替代方案:对象本身是一组数据,所有私有。只有Proxy
类(朋友)可以读取/写入其数据,并且在初始化Proxy
时,它会自动抓取锁定(在对象的互斥锁上)。
class Data {
friend class Proxy;
Mutex _mutex;
int _bar;
};
class Proxy {
public:
Proxy(Data& data): _lock(data._mutex), _data(data) {}
int bar() const { return _data._bar; }
void bar(int b) { _data._bar = b; }
private:
Proxy(Proxy const&) = delete; // disable copy
Lock _lock;
Data& _data;
};
答案 2 :(得分:0)
如果我想做你正在做的事情,我会做以下其中一项。
方法1:
shared_mutex m; // somewhere outside the class
class A
{
private:
int variable;
public:
void lock() { m.lock(); }
void unlock() { m.unlock(); }
bool is_locked() { return m.is_locked(); }
bool write_to_var(int newvalue)
{
if (!is_locked())
return false;
variable = newvalue;
return true;
}
bool read_from_var(int *value)
{
if (!is_locked() || value == NULL)
return false;
*value = variable;
return true;
}
};
方法2:
shared_mutex m; // somewhere outside the class
class A
{
private:
int variable;
public:
void write_to_var(int newvalue)
{
m.lock();
variable = newvalue;
m.unlock();
}
int read_from_var()
{
m.lock();
int to_return = variable;
m.unlock();
return to_return;
}
};
第一种方法更有效(不是一直锁定 - 解锁),但是,程序可能需要不断检查每次读写的输出以查看它们是否成功。第二种方法自动处理锁定,因此程序员甚至不知道锁是否存在。
注意:这不是复制粘贴的代码。它展示了一个概念并概述了它是如何完成的。请不要发表评论说你忘了在某处检查错误。
答案 3 :(得分:0)
这听起来很像Alexandrescu对volatile
的看法。你不是
使用const
的实际语义,而是利用它的方式
类型系统使用它。在这方面,我更喜欢Alexandrescu的使用
volatile
:const
具有非常明确且易于理解的语义,
并且颠覆它们肯定会给任何读书的人带来困惑
或维护代码。 volatile
更合适,因为它没有
定义良好的语义,并且在大多数应用程序的上下文中,不是
用于其他任何事情。
而不是返回一个经典的代理对象,你应该返回一个
智能指针。实际上你可以使用shared_ptr
来获取它
在返回值之前锁定,并在删除器中释放它
(而不是删除对象);然而,我宁愿害怕这一点
会引起读者之间的混淆,我可能会去
使用自定义智能指针(可能使用shared_ptr
自定义
执行中的删除)。 (根据你的描述,我怀疑
无论如何,这更接近你的想法。)