如何避免 C++ 匿名对象
Posted
技术标签:
【中文标题】如何避免 C++ 匿名对象【英文标题】:How to avoid C++ anonymous objects 【发布时间】:2013-04-24 10:34:15 【问题描述】:我有一个ScopedLock
类,它可以帮助在超出范围时自动释放锁定。
但是,问题是:有时团队成员会编写无效的锁定代码,例如
ScopedLock(mutex); // anonymous
xxx;
上面的代码是错误的,因为ScopedLock
对象是立即被构造和销毁的,所以它未能锁定预期的区域(xxx
)。我希望编译器在尝试编译此类代码时给出错误。这个可以吗?
我搜索了g++
警告选项,但没有找到正确的选项。
【问题讨论】:
我不认为你可以禁止这个(甚至生成编译器诊断)。一个更有效(并且可能更令人满意)的解决方案是在你的同事这样做时打他们一巴掌,直到他们最终停止这样做。 ;) 顺便说一句,实际名称是临时对象而不是匿名对象。 不幸的是,lists.cs.uiuc.edu/pipermail/cfe-dev/2010-December/012755.html 似乎从未进入 Clang。 当然,ScopedLock(mutex), foo();
是有效的用法。单独来看它从来没有必要,但在更复杂的表达式中它可以非常方便:Bar(2, (ScopedLock(mutex), foo()), 3)
.
P0577 建议通过重新利用 register
关键字来表示“将以下匿名对象与整个范围相关联”来避免这种情况。
【参考方案1】:
我在一个代码库中看到了一个有趣的技巧,但它仅在您的 scoped_lock 类型不是模板时才有效(std::scoped_lock 是)。
#define scoped_lock(x) static_assert(false, "you forgot the variable name")
如果你正确使用这个类,你就有
scoped_lock lock(mutex);
并且由于 scoped_lock 标识符后面没有打开括号,宏不会触发并且代码将保持原样。如果你写\
scoped_lock(mutex);
宏将触发,代码将被替换为
static_assert(false, "you forgot the variable name");
这将生成一条信息性消息。
如果您使用限定名称
threads::scoped_lock(mutext);
那么结果仍然无法编译,但消息不会那么好。
当然,如果你的锁是模板,那么坏代码就是
scoped_lock<mutex_type>(mutex);
不会触发宏。
【讨论】:
我不知道甚至可以写没有括号的宏名称并且它仍然有效(并且没有扩展)。这是一个不错的。 +1 我能想到的其他陷阱:Typedefs 将失败,除非同时引入新的宏。此外,当您从具有此类宏的类继承时,您必须首先使用 typedef(如果不这样做,AFAIK 您不能使用互斥锁作为参数调用超级构造函数)以及 sub类的把戏再次失败。在这两种情况下(typedef + 子类),您当然可以编写另一个宏并且再次安全,但一如既往:您必须首先记住它。 我不知道std::scoped_lock
是在这个问题出现的时候吗?
@Walter 对我来说可能是一个错误,将它与 Boost.Thread 的原始名称混淆了。 std 有 unique_lock 和 lock_guard。
作为同事指针,这也不会触发scoped_locmutex;
【参考方案2】:
不,很遗憾没有办法做到这一点,as I explored in a blog post last year。
在里面,我总结道:
我猜这个故事的寓意是在使用
scoped_lock
s 时记住这个故事。
您可以尝试强制团队中的所有程序员使用宏或 range-for 技巧,但如果您能保证在每种情况下都能做到这一点,那么您也可以保证在每种情况下都能捕捉到这个错误.
您正在寻找一种方法来以编程方式在这个特定错误发生时发现它,但没有。
【讨论】:
【参考方案3】:您可以使用同名的类和已删除的函数。不幸的是,这需要在类型之前添加“class”关键字。
class Guard
public:
explicit Guard(void)
;
static void Guard(void) = delete;
int main()
// Guard(); // Won't compile
// Guard g; // Won't compile
class Guard g;
【讨论】:
【参考方案4】:为避免这种情况,请引入一个为您执行此操作的宏,始终为储物柜使用相同的名称:
#define LOCK(mutex) ScopedLock _lock(mutex)
然后像这样使用它:
LOCK(mutex);
xxx;
作为替代方案,可以使用宏构造来模拟 Java 的 synchronize
块:在总是只运行一次的 for 循环中,我在 for 循环的初始化语句中实例化了这样一个锁,所以它在离开for循环。
但是,它有一些陷阱,break
语句的意外行为就是一个例子。这个“hack”介绍here。
当然,上述方法都不能完全避免像您的示例那样的意外代码。但是,如果您习惯于使用这两个宏之一来编写锁定互斥锁,就不太可能发生这种情况。由于 locker 类的名称将永远不会出现在代码中,除非在宏定义中,您甚至可以在版本控制系统中引入提交挂钩以避免提交无效代码。
【讨论】:
实际上,使用新的基于范围的循环 (***.com/a/9657748/46642) 好的,但是你必须记住使用LOCK
而不是ScopedLock
。我不认为你真的解决了什么问题。
+1,有趣的synchronize
把戏。但是由于它引入的陷阱和新的非标准语义,我不推荐使用它。至于LOCK
宏,像往常一样在后台生成局部变量时,它应该根据文件名和行创建一个标识符名称,以减少可能的名称冲突(现在不记得预处理器语法了)。跨度>
@leemes:我不是这个意思,因为宏。我的意思是因为直到最后一段你都没有解决这个问题!
非常感谢。我想我可以定义#define ScopeLock(x) wrong_usage_lock,所以当团队成员编写ScopedLock(x)时,编译器会抛出错误。【参考方案5】:
AFAIK 在 gcc 中没有这样的标志。静态分析器可能更适合您的需求。
【讨论】:
【参考方案6】:在 C++17 中,可以将类型标记为 [[nodiscard]]
,在这种情况下,鼓励对丢弃该类型值的表达式发出警告(包括此处描述的类似于变量声明的情况) .在 C++20 中,它也可以应用于单个构造函数,只要它们中的一些会导致此类问题。
【讨论】:
【参考方案7】:用宏替换它
#define CON2(x,y) x##y
#define CON(x,y) CON2(x,y)
#define LOCK(x) ScopedLock CON(unique_,__COUNTER__)(mutex)
用法
LOCK(mutex);
//do stuff
此宏将为锁生成唯一名称,允许锁定内部范围内的其他互斥锁
【讨论】:
这并不比“添加变量名”有用。它没有提供捕获错误的方法。 如果他们没有错过变量名,他们就不会错过变量名。关键是你必须记住在这两种情况下做某事。问题是关于以编程方式检测错误。 他们必须记得首先使用宏。 使用智能指针并不是“如何让我的编译器捕获所有不正确使用的原始指针?”的答案。当然,这并不意味着这不是一个好主意;) @kassak Lightness Races in Orbit 是正确的。在引入宏时,我首先在回答中犯了完全相同的错误。然后我意识到:“嘿,但现在程序员仍然必须使用宏,而他仍然允许编写问题中的代码。现在我该如何避免它?” => 只需禁止在没有宏的情况下使用储物柜,这只有在存在这样的宏时才有可能。因此,解决这个问题是一个额外的要求。只引入宏不是答案。在我看来,Orbit 中的 Lightness Races 并不是在拖钓,而是在解释差异。以上是关于如何避免 C++ 匿名对象的主要内容,如果未能解决你的问题,请参考以下文章