如何避免 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_locks 时记住这个故事。


您可以尝试强制团队中的所有程序员使用宏或 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++ 匿名对象的主要内容,如果未能解决你的问题,请参考以下文章

C++匿名对象解析

C++类和对象:构造函数初始化友元匿名对象内部类

C++类和对象:构造函数初始化友元匿名对象内部类

团队合作-如何避免JS冲突

C++类与对象第四篇:(初始化列表构造匿名对象隐式类型转换友元static成员内部类)

C++中引用和匿名对象的理解和本质剖析