禁止创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! 
Interesting Posts