【问题标题】:Good or Bad C++ Idiom - Objects used purely for constructor/destructor?好的或坏的 C++ 成语 - 纯粹用于构造函数/析构函数的对象?
【发布时间】:2010-09-30 22:09:10
【问题描述】:

我有几个类除了在它们的构造函数/析构函数中什么都不做。这是一个例子

class BusyCursor 
{
  private:
    Cursor oldCursor_;

  public:

    BusyCursor()
    {
      oldCursor_ = CurrentCursor();
      SetCursor(BUSY_CURSOR);
    }
    ~BusyCursor()
    {
      SetCursor(oldCursor_);
    }
}

// example of use
    void DoSlowThing
    {
      BusyCursor busy;
      ... do something time-consuming  ...
    }

我有点担心未来的可读性。我在这里是不是太“狡猾”了,变量(“忙”)从未在代码中实际使用过?一些静态分析工具是否会建议将它们删除,或者这个习语是否足够普遍不用担心?

【问题讨论】:

  • 这正是 MFC 的 CWaitCursor 类的工作原理。

标签: c++ idioms raii


【解决方案1】:

这种技术非常普遍,被称为设计模式:Resource Acquisition Is Initialization (RAII)

我会毫不犹豫地使用这种设计模式。

使用这种设计模式进行编码会更好,因为您可以避免忘记重置光标或任何相关资源的错误。

如果您担心其他程序员可能不理解它,那么这些程序员应该接受更多的教育。始终努力以最无错误的方式编写代码,这样您和其他人就不可能在自己/自己的脚下开枪。


“一些静态分析工具可以建议将它们删除吗?”

  • 没有静态分析工具会将此视为问题。
  • 不会给出编译器警告
  • 没有编译器优化会导致任何问题。

原因是因为创建了对象并调用了构造函数/析构函数。所以它不是一个未引用的变量。

【讨论】:

  • 我正在使用这样的类/对象来自动进入(在 ctor 上)和离开(dtor)关键部分或提高/降低信号量。这样即使异常发生,析构函数也会释放资源。
  • 我非常同意; RAII 非常有用。如果你经常使用它需要注意的事情: - 调用可以在你的析构函数中抛出异常的代码(导致异常中的异常)。 - 不要试图通过非确定性析构函数将技巧带到垃圾收集语言中!
  • 谢谢 - 我通常会想到 RAII wrt 文件或作用域指针,您在其中积极使用作用域内的对象。我只是不确定一个明显“未使用的变量”的想法是否可行,
【解决方案2】:

成语不错,常用。

它比任何替代方法都好,例如即使您的一些耗时代码抛出异常,~BusyCursor 析构函数仍将被调用。

【讨论】:

    【解决方案3】:

    没有理智的静态分析工具会建议删除该变量,因为它使用了。它之所以有效,是因为它的构造函数和析构函数都被调用了。你应该很安全。

    【讨论】:

      【解决方案4】:

      我通常将此称为“守卫”。在我看来,它展示了 C++ 的最大优势之一(确定性资源处理)。这是我在使用垃圾收集语言工作时最怀念的事情之一。

      【讨论】:

        【解决方案5】:

        其他人已经提到这是经典的 RAII。 我要补充的是,这是关于 C++ 最好的事情之一。很少有其他语言支持它,或者至少正确支持它(甚至 C# 的 using 构造也没有那么好,因为负担仍然在客户端代码上 - see my blog entry on this)。

        它与 C++ 的联系如此紧密,以至于您应该确信任何阅读它的人都会熟悉它——如果不是,他们应该熟悉。

        【讨论】:

        • python 通过 with 关键字提供了类似的功能
        【解决方案6】:

        正如其他人所说,这是很好的 C++ 风格。为了提高可读性,我总是在此类仅限 RAII 的类前面加上 Scoped(例如,ScopedBusyCursor),以便一目了然地清楚该类的目的是什么。

        【讨论】:

          【解决方案7】:

          可以说不使用这种模式是不好的习惯用法。当您不使用 RAII 时,您的代码最终看起来像这样:

          void func() {
              Cursor oldCursor = CurrentCursor();
              SetCursor(BUSY_CURSOR);
              try {
                  do_slow_stuff();
                  SetCursor(oldCursor);
              } catch (...) {
                  SetCursor(oldCursor);
                  throw;
              }
          }
          

          你真的认为在你的代码中乱扔垃圾更利于维护吗?

          【讨论】:

            【解决方案8】:

            这是一个众所周知的良好 C++ 习语,就像其他人回答的那样。

            为了清楚地表明这些类只能在一个范围内使用而不是在不同的范围之间移动,最好让它们不可复制。这可以通过添加未实现的私有复制构造函数和复制分配运算符来手动完成。更短更易读的方法是从boost::noncopyable派生类:

            #include <boost/noncopyable.hpp>
            class BusyCursor : public boost::noncopyable // for scoped use only
            {
                // ...
            };
            

            【讨论】:

              【解决方案9】:

              您也可以使用像 Andrei Alexandrescu 和 Petru Marginean 的 ScopeGuard 这样的东西。您的示例将如下所示:

              void DoSlowThing
              {     
                  Cursor oldCursor = CurrentCursor();
                  SetCursor(BUSY_CURSOR);
                  ON_BLOCK_EXIT(SetCursor, oldCursor);
                  ... do something time-consuming  ...
              }
              

              这使得一次性 RAII 类型的操作更加容易,而无需为每个操作创建一个新类。但是,对于您的 Cursor 示例,由于它是一个您可能会重复使用多次的类,因此使用专用类可能会更好。

              【讨论】:

                猜你喜欢
                • 2013-09-27
                • 2018-12-09
                • 2013-08-02
                • 2017-04-27
                • 1970-01-01
                • 2016-12-28
                • 2011-02-20
                • 2011-12-17
                • 1970-01-01
                相关资源
                最近更新 更多