问题 如何避免C ++匿名对象


我有一个 ScopedLock 当超出范围时,可以帮助自动释放锁的类。 但问题是:有时团队成员会编写无效的锁码,例如

{
    ScopedLock(mutex);   // anonymous
    xxx;
}

上面的代码是错误的,因为 ScopedLock 对象被构造和破坏 立即,因此无法锁定预期区域(xxx)。我希望编译器在尝试编译此类代码时发出错误。可以这样做吗?

我搜索过了 g++ 警告选项,但找不到合适的选项。


9183
2018-04-24 10:34


起源

我不认为你可以禁止这个(甚至生成编译器诊断)。一个更有效率(也许是令人满意的)解决方案就是在他们这样做时拍打你的同事,直到他们最终停止这样做。 ;) - syam
BTW,实际名称是 临时对象 不 匿名对象。 - syam
不幸的是,它看起来像 lists.cs.uiuc.edu/pipermail/cfe-dev/2010-December/012755.html 从来没有成为Clang。 - Sebastian Redl
当然,这是有效的用途 ScopedLock(mutex), foo();。孤立无援,但它在更复杂的表达式中非常方便: Bar(2, (ScopedLock(mutex), foo()), 3)。 - MSalters


答案:


为避免这种情况,请引入一个为您执行此操作的宏,始终使用与锁定器相同的名称:

#define LOCK(mutex) ScopedLock _lock(mutex)

然后像这样使用它:

{
    LOCK(mutex);
    xxx;
}

作为替代,Java的 synchronize 可以使用宏构造模拟块:在for循环运行中总是只运行一次,我在for循环的初始化语句中实例化这样一个锁定器,因此在离开for循环时它会被破坏。

然而,它有一些陷阱,一个意想不到的行为 break 声明就是一个例子。这个“黑客”被引入 这里


当然,上述方法都没有像你的例子那样完全避免意外代码。但是,如果您习惯使用两个宏中的一个来编写锁定互斥锁,则不太可能发生。因为除了宏定义之外,锁定器类的名称将永远不会出现在代码中,您甚至可以在版本控制系统中引入提交挂钩以避免提交无效代码。


2
2018-04-24 10:42



实际上,这种for循环的滥用可以在没有新的基于范围的循环的缺陷的情况下完成(stackoverflow.com/a/9657748/46642) - R. Martinho Fernandes
好的,但是你必须记得使用 LOCK 代替 ScopedLock。我不认为你真的解决了什么。 - Lightness Races in Orbit
@leemes:我不是故意因为宏。我的意思是因为直到最后一段你没有解决这个问题! - Lightness Races in Orbit
@leemes:没问题! - Lightness Races in Orbit
非常感谢你。我想我可以定义#define ScopeLock(x)wrong_usage_lock,所以当团队成员编写ScopedLock(x)时,编译器会抛出错误。 - Raymond


我在一个代码库中看到了一个有趣的技巧,但只有当你的scoped_lock类型不是模板时才有效(std :: scoped_lock是)。

#define scoped_lock(x) static_assert(false, "you forgot the variable name")

如果您正确使用该类,则可以

scoped_lock lock(mutex);

并且由于scoped_lock标识符后面没有打开的paren,宏不会触发,代码将保持原样。如果你写

scoped_lock(mutex);

宏将触发,代码将被替换

static_assert(false, "you forgot the variable name");

这将生成信息性消息。

如果您使用限定名称

threads::scoped_lock(mutext);

然后结果仍然无法编译,但消息不会那么好。

当然,如果您的锁是模板,那么错误的代码就是

scoped_lock<mutex_type>(mutex);

这不会触发宏。


5
2018-04-24 12:30



我不知道甚至可以在没有括号的情况下编写宏名称,它仍然有效(而不是扩展)。这是一个很好的。 +1 - leemes
我能想到的其他陷阱:除非同时引入新的宏,否则Typedef将失败。此外,当您从具有此类宏的类继承时,您必须首先使用typedef(如果不这样,AFAIK,您不能使用互斥锁作为参数调用超级构造函数)以及子类阶级伎俩再次失败。在这两种情况下(typedef + sub class),您当然可以编写另一个宏并再次安全,但是一如既往:您必须首先记住它。 - leemes
我不知道 std::scoped_lock 在这个问题的时代回来了吗? - Walter
@Walter可能是我的错误,将它与Boost.Thread的原始名称混淆。 std有unique_lock和lock_guard。 - Sebastian Redl


不,不幸的是 没有办法做到这一点正如我去年在博客中探讨过的那样

在其中,我得出结论:

我想这个故事的寓意是要记住 这个 使用时的故事 scoped_lock秒。


您可以尝试强制团队中的所有程序员使用宏或范围换技巧,但如果您可以保证在每种情况下都可以保证在每种情况下都能捕获此错误。

你正在寻找一种方法 编程 当它被制作时捕捉到这个特定的错误,并且没有。


4
2018-04-24 10:45





您可以使用具有相同名称的类和已删除的函数。不幸的是,这需要在类型之前添加“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;
}

2
2017-11-30 19:10





AFAIK在gcc中没有这样的旗帜。静态分析仪可能更适合您的需求。


1
2018-04-24 10:36





用宏替换它

#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
}

此宏将为锁生成唯一的名称,允许锁定内部作用域中的其他互斥锁


0
2018-04-24 10:47



如果他们没有错过变量名,他们就不会错过变量名。关键在于 你必须记得做点什么 在这两种情况下。问题是以编程方式检测错误。 - Lightness Races in Orbit
他们必须记住首先使用宏。 - Lightness Races in Orbit
使用智能指针不是“如何使我的编译器捕获原始指针的所有错误使用?”的答案。当然,这并不意味着它不是一个好主意;) - Lightness Races in Orbit
我是认真的! :-) - Lightness Races in Orbit
在轨道上的@kassak轻盈赛是正确的。在介绍宏时,我在回答中首先犯了同样的错误。然后我意识到:“嘿,但现在程序员还在 具有 在他的时候使用宏 仍然允许编写问题中的代码。现在我该如何避免呢?“=>只是禁止使用没有宏的锁定器,这只有在存在这样一个宏的情况下才有可能。所以这是解决问题的另一个要求。仅引入宏不是答案在我看来,轨道中的轻盈比赛并不是一种拖曳,而是解释了它的不同之处。 - leemes