【问题标题】:Use the destructor to do work [closed]使用析构函数做工作[关闭]
【发布时间】:2016-01-24 18:33:17
【问题描述】:

我有一个包含一组请求的事务类。 每 X 秒设置一次当前事务中的请求,并清除请求列表。

是否可以设计 Transaction 类,使其在析构函数销毁期间发送事务中的请求?

这意味着我们分配一个新的事务,只要它还活着就向它添加新的请求,一旦调用析构函数,所有的请求都会被发送。

这样我们可以保证:

  1. 所有交易都已发送(只要我们不泄露交易对象)。
  2. 交易发送后无法对其添加更改。

这被认为是一种好的做法吗?还是使用 SendRequests 方法发送所有请求并清除列表会更好?

【问题讨论】:

  • “是否可以将 Transaction 类设计为在析构函数销毁期间发送事务中的请求?” 不 - 你为什么要这么想?
  • 这是个糟糕的主意。想象您开始一个事务,在其中执行一些所需的操作,然后抛出一些东西。你的析构函数被调用并提交了一个不完整的混乱......

标签: c++ raii object-oriented-analysis


【解决方案1】:

析构函数在 C++ 语言中只有一个用途。那就是管理(因此它可以记录,使用帮助对象或函数,)根据 RAII 在构造函数中获取的资源的释放。任何其他用途迟早会给您带来麻烦。

【讨论】:

  • 这就是我的想法。谢谢!
  • ...除非您非常了解该标准并且可以将此工具用于其他目的(如基准测试、计数等)而不会遇到任何问题。
  • @bobah 为什么不使用模板化包装器而不是污染 dtor 呢?这样你就可以保持通用。而这些恶作剧很容易让你陷入困境,无论你多么认为“你很了解标准”。
  • @PaulEvans - 基于 RAII 的工具通常更容易分解以供重用。包装器仅在 C++14 中成为“零开销”,其中完美的参数和返回值转发是可能的。
  • @bobah 好点,我同意有时我们必须妥协。但我说的是最佳实践。
【解决方案2】:

您通常会做完全相反的事情——明确肯定分支(导致提交的分支),但有基于 RAII 的回滚分支以保证一致的提交或回滚行为。主要是因为从析构函数中抛出和实现无抛出回滚通常比对提交执行相同操作更容易。

【讨论】:

    【解决方案3】:

    您应该只执行在类的析构函数中实际解构手头对象的操作。

    析构函数仅用于清理任务仅涉及当前对象。在此类对象的生命周期结束时触发外部通信肯定会给您带来麻烦。

    一个示例问题发生在子类化和多态性期间:

    #include <iostream>
    
    class A {
    public:
      A() {
        std::cout << "Construct A" << std::endl;
      }
    
      virtual ~A() {
        std::cout << "Deconstruct A" << std::endl;
        this->doWork();
      }
    
      virtual void doWork() {
        std::cout << "Dummy virtual worker function in A" << std::endl;
      }
    };
    
    class B : public A {
    public:
      B() {
        std::cout << "Construct B" << std::endl;
      }
    
      virtual ~B() {
        std::cout << "Deconstruct B" << std::endl;
      }
    
      void doWork() {
        std::cout << "Actual worker function in B" << std::endl;
      };
    };
    
    int main(int argc, char** argv) {
      A* aTest = new B();
      aTest->doWork();
    
      delete aTest;
    
      return 0;
    }
    

    这会导致输出

    Construct A
    Construct B
    Actual worker function in B
    Deconstruct B
    Deconstruct A
    Dummy virtual worker function in A
    

    因此,当您子类化您的类并覆盖其中的虚函数时,当您到达基类的析构函数时,您将失去被覆盖的功能。在示例中,当在 A 的析构函数中调用 this-&gt;doWork() 时会发生这种情况。

    这可能会弄乱您的数据、流程或您的班级正在做的任何事情。因此,我再次建议不要在析构函数中触发实际工作。最好在类中为此定义一个单独的函数,并在为此特定任务指定的时间调用它。否则,您的代码只会失去很多可读性和可维护性。

    【讨论】:

      【解决方案4】:

      一方面,技术上的可能性:

      12.7/4:可以在构造或销毁期间调用成员函数,包括虚函数。

      另一方面,每个设计都应该尊重的指导原则:

      12.4/15 一旦为对象调用析构函数,该对象就不再存在;

      因此,我不建议这样的设计。

      这样的设计是不好的做法。如果创建了事务,并向其中添加了一些请求,如果出于任何原因需要中断事务(连接的系统消失,用户想要中断,发生异常等......),您的设计将强制要执行的不完整请求。这不尊重人们从交易中期望的全部或全部逻辑。

      更好的方法是根据 state design pattern 设计您的交易:例如,交易将具有以下状态:

      • 已创建(新,无请求),
      • 正在进行(正在添加请求),
      • 待执行(不会执行额外的请求),
      • 并已完成(请求已执行)或已取消。

      如果调用析构函数并且状态未完成也未取消,则析构函数应争取取消。

      【讨论】:

        猜你喜欢
        • 2020-06-20
        • 1970-01-01
        • 1970-01-01
        • 2022-11-20
        • 1970-01-01
        • 2015-07-15
        • 2013-08-06
        • 2018-12-09
        • 2014-06-30
        相关资源
        最近更新 更多