禁止创build临时对象
在multithreading应用程序中debugging崩溃时,我终于在这个语句中find了问题:
CSingleLock(&m_criticalSection, TRUE);
请注意,它正在创buildCSingleLock类的未命名对象,因此临界区对象在此语句后立即解锁。 这显然不是编码员想要的。 这个错误是由一个简单的input错误造成的。 我的问题是,有没有办法,我可以防止在编译时创build一个类的临时对象,即上述types的代码应该会产生一个编译器错误。 总的来说,我认为每当一个class级试图做某种资源获取,那么这个class级的临时对象就不应该被允许。 有没有办法强制执行?
编辑:正如j_random_hacker注意到的,可以强制用户声明一个命名对象来取出一个锁。
但是,即使您的class级禁止创build临时对象,用户也可能犯下类似的错误:
// take out a lock: if (m_multiThreaded) { CSingleLock c(&m_criticalSection, TRUE); } // do other stuff, assuming lock is held
最终,用户必须了解他们编写的一行代码的影响。 在这种情况下,他们必须知道他们正在创build一个对象,他们必须知道它持续多久。
另一个可能的错误
CSingleLock *c = new CSingleLock(&m_criticalSection, TRUE); // do other stuff, don't call delete on c...
这将导致你问“有什么办法可以阻止我的课程的用户从堆上分配”? 答案将是相同的。
在C ++ 0x中,通过使用lambdaexpression式,还有另一种方法可以做到这一点。 定义一个函数:
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. });
这对用户来说太难了。 最初的语法看起来很奇怪,但[&]后跟一个代码块意味着“定义一个不带参数的函数,如果我通过名称来引用任何东西,并且它是外部事物的名称(例如,包含函数)让我通过非const引用来访问它,所以我可以修改它。)
首先, Earwicker提出了一些好的观点 – 你不能防止每一次偶然的误用这个结构。
但是对于您的具体情况,事实上可以避免。 这是因为C ++对临时对象做了一个(奇怪的)区分: Free函数不能将非const引用引用到临时对象。 所以,为了避免出现locking,只需将锁定代码从CSingleLock
构造函数中移出,并将其移入一个自由函数(可以使朋友避免将内部函数暴露为方法):
class CSingleLock { friend void Lock(CSingleLock& lock) { // Perform the actual locking here. } };
解锁仍然在parsing器中执行。
使用:
CSingleLock myLock(&m_criticalSection, TRUE); Lock(myLock);
是的,写起来稍微笨拙。 但是现在,如果你尝试下面的话,编译器会报错:
Lock(CSingleLock(&m_criticalSection, TRUE)); // Error! Caught at compile time.
因为Lock()
的非const参数不能绑定到临时的。
也许令人惊讶的是,类方法可以在临时对象上运行 – 这就是Lock()
需要成为一个自由函数的原因。 如果您将friend
说明符和函数参数放在最上面的代码片段中以使Lock()
成为方法,那么编译器会很高兴地允许您编写:
CSingleLock(&m_criticalSection, TRUE).Lock(); // Yikes!
MS编译器注意:直到Visual Studio .NET 2003的MSVC ++版本错误地允许函数绑定到VC ++ 2005之前的版本中的非const引用。 此行为已在VC ++ 2005及更高版本中修复 。
不,没有办法做到这一点。 这样做会打破几乎所有严重依赖创build无名的临时对象的C ++代码。 你对特定类的唯一解决scheme是使它们的构造函数是私有的,然后通过某种工厂来构造它们。 但我认为治疗比疾病更糟!
我不这么认为。
虽然这不是一个明智的做法 – 正如你发现你的错误 – 这个声明没有任何“非法”的东西。 编译器无法知道该方法的返回值是否“重要”。
编译器不应该禁止临时对象的创build,恕我直言。
特别是缩小vector的情况下,您确实需要创build临时对象。
std::vector<T>(v).swap(v);
虽然有点困难,但是代码审查和unit testing应该能够解决这些问题。
否则,这是一个穷人的解决办法:
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
我看到5年来没有人提出最简单的解决scheme:
#define LOCK(x) CSingleLock lock(&x, TRUE); ... void f() { LOCK(m_criticalSection);
现在只能使用这个macros来创build锁。 没有机会再创造临时工了! 这还有一个额外的好处,就是macros可以很容易地扩展到在debugging版本中执行任何types的检查,例如检测不合适的recursionlocking,logging文件和锁的行,等等。
那么下面呢? 稍微滥用预处理器,但是我认为它应该被包含在内:
class CSingleLock { ... }; #define CSingleLock class CSingleLock
现在忘记命名临时结果的错误,因为当以下是有效的C ++:
class CSingleLock lock(&m_criticalSection, true); // Compiles just fine!
相同的代码,但省略名称不是:
class CSingleLock(&m_criticalSection, true); // <-- ERROR!