【问题标题】:How to avoid C++ anonymous objects如何避免 C++ 匿名对象
【发布时间】: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 关键字来表示“将以下匿名对象与整个范围相关联”来避免这种情况。

标签: c++ g++


【解决方案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_loc{mutex};
【解决方案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 类的名称将永远不会出现在代码中,除非在宏定义中,您甚至可以在版本控制系统中引入提交挂钩以避免提交无效代码。

      【讨论】:

      • 实际上,使用新的基于范围的循环 (stackoverflow.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 并不是在拖钓,而是在解释差异。
          猜你喜欢
          • 2016-05-06
          • 1970-01-01
          • 2013-07-07
          • 2021-02-02
          • 1970-01-01
          • 1970-01-01
          • 2021-02-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多