题
在调试多线程应用程序中的崩溃时,我终于在以下语句中找到了问题:
CSingleLock(&m_criticalSection, TRUE);
请注意,它正在创建 CSingleLock 类的未命名对象,因此临界区对象在此语句之后立即解锁。这显然不是程序员想要的。此错误是由一个简单的打字错误引起的。我的问题是,是否可以通过某种方式阻止在编译时创建类的临时对象,即上述类型的代码应该会生成编译器错误。一般来说,我认为每当一个类尝试进行某种资源获取时,就不应该允许该类的临时对象。有什么办法可以强制执行吗?
解决方案
修改:强>如图j_random_hacker笔记,可以强制用户为了取出一个锁来声明命名对象
然而,即使临时工的创作在某种程度上禁止类,那么用户可以做一个类似的错误:
// take out a lock:
if (m_multiThreaded)
{
CSingleLock c(&m_criticalSection, TRUE);
}
// do other stuff, assuming lock is held
最终,用户必须了解的代码行,他们写的影响。在这种情况下,他们必须知道他们正在创建一个对象,他们必须知道会持续多久。
另一个可能的错误:
CSingleLock *c = new CSingleLock(&m_criticalSection, TRUE);
// do other stuff, don't call delete on c...
这将导致你问“有什么办法,我可以从堆中分配它停我的课的用户”?这个问题的答案将是相同的。
在的C ++ 0x会有另一种方式做到这一切,通过使用lambda表达式。定义一个函数:
template <class TLock, class TLockedOperation>
void WithLock(TLock *lock, const TLockedOperation &op)
{
CSingleLock c(lock, TRUE);
op();
}
这功能捕获CSingleLock的正确用法。现在,让用户做到这一点:
WithLock(&m_criticalSection,
[&] {
// do stuff, lock is held in this context.
});
这是更难为用户弄糟。语法看起来奇怪在第一,但[b],然后一个代码块的意思是“定义一个函数,无参数,如果我指的是什么名字,它是东西之外的名称(例如,在包含一个局部变量功能)让我通过非const引用访问它,所以可对其进行修改。)
其他提示
第一的, Earwicker 提出了一些很好的观点 ——你无法阻止这种结构的每一次意外误用。
但对于你的具体情况,这实际上是可以避免的。这是因为 C++ 对于临时对象确实做出了一个(奇怪的)区别: 自由函数不能采用对临时对象的非常量引用。 因此,为了避免锁突然出现或消失,只需将锁定代码移出 CSingleLock
构造函数并转换为自由函数(您可以将其作为朋友来避免将内部暴露为方法):
class CSingleLock {
friend void Lock(CSingleLock& lock) {
// Perform the actual locking here.
}
};
解锁仍然在析构函数中执行。
使用方法:
CSingleLock myLock(&m_criticalSection, TRUE);
Lock(myLock);
是的,写起来有点麻烦。但现在,如果您尝试以下操作,编译器会抱怨:
Lock(CSingleLock(&m_criticalSection, TRUE)); // Error! Caught at compile time.
因为非常量 ref 参数 Lock()
无法绑定到临时对象。
也许令人惊讶的是,类方法 能 对临时对象进行操作——这就是为什么 Lock()
需要是一个自由函数。如果您删除 friend
顶部代码片段中的说明符和函数参数 Lock()
一个方法,那么编译器会很高兴地允许你写:
CSingleLock(&m_criticalSection, TRUE).Lock(); // Yikes!
MS 编译器注意事项: Visual Studio .NET 2003 之前的 MSVC++ 版本错误地允许函数绑定到 VC++ 2005 之前版本中的非常量引用。 此行为已在 VC++ 2005 及更高版本中修复.
没有,没有这样做的方式。这样做会破坏几乎是严重依赖于创建无名的临时所有的C ++代码。您的特定类唯一的解决办法就是让它们的构造私人,然后总是通过一些工厂建造它们。但我认为治愈是比疾病更糟糕!
我不这么认为。
虽然这不是一个明智的做法 - 因为你已经和你的bug发现 - 没有什么“非法”的有关语句。编译器无法知道从该方法的返回值是否为“重要的”或不的方式。
编译器不应该禁止临时对象创建,IMHO。
特殊情况下,像缩小向量你真的需要临时对象被创建。
std::vector<T>(v).swap(v);
虽然这是有点困难,但仍代码审查和单元测试应该抓住这些问题。
另外,这里是一个穷人的解决方案:
CSingleLock aLock(&m_criticalSection); //Don't use the second parameter whose default is FALSE
aLock.Lock(); //an explicit lock should take care of your problem
有关下列哪些?稍微滥用职权预处理器,但它是足够聪明的,我认为它应该包括:
class CSingleLock
{
...
};
#define CSingleLock class CSingleLock
现在遗忘命名一个错误的临时结果,因为虽然以下是有效的C ++:
class CSingleLock lock(&m_criticalSection, true); // Compiles just fine!
在相同的代码,但省略了名称,是不是:
class CSingleLock(&m_criticalSection, true); // <-- ERROR!
我看到,在5年内没有人拿出了最简单的解决方案:
#define LOCK(x) CSingleLock lock(&x, TRUE);
...
void f() {
LOCK(m_criticalSection);
现在只能用这个宏创建锁。没有机会创造的临时更多!这具有宏可以很容易地扩充以执行任何种类的在调试检查的构建,例如检测不适当的递归锁定的附加益处,记录文件和锁定的线,以及更多。