【问题标题】:How to avoid shared pointers in C++ caused by try/catch?如何避免 C++ 中由 try/catch 引起的共享指针?
【发布时间】:2022-01-10 09:50:16
【问题描述】:

我使用共享指针,因为变量只能存在于创建它的块中。

int main(void) {

    std::shared_ptr<project::log::Log> log;

    try {
        log = make_shared<annmu::log::Log>("error.log"); // can throw eception
    }
    catch(std::exception &e) {
        std::cout << "Error\n\n";
        return 0;
    }

}

我想避免共享指针并创建更简单的代码。类似于下面的代码(不工作的代码)。

int main(void) {

    project::log::Log log; // can throw eception

    try {
        log = project::log::Log("error.log"); // can throw eception
    }
    catch(std::exception &e) {
        std::cout << "Error\n\n";
        return 0;
    }

}

这是避免共享指针的好方法吗?它是更有效的解决方案吗?在第二种解决方案中,对象被创建了两次。

谢谢你的回答。

【问题讨论】:

  • 为什么不在try 块内简单地定义log 变量?
  • 因为它可以抛出异常。
  • 是的,这就是为什么你要在try 块中创建它,对吧?那么为什么不简单地在try 块内定义变量呢?如try { project::log::Log log("error.log"); /* Other code... */ }
  • 把所有东西都写下来试试{}是个好习惯吗?并做嵌套的尝试{}捕捉{}?
  • 如果你在嵌套 try/catch 块,你很可能遇到了结构问题,需要重新考虑。

标签: c++ reference try-catch shared-ptr


【解决方案1】:

避免使用共享指针总是好的做法,除非您实际共享指针。您可以使用 unique_ptr 作为替代品。
拥有一个非抛出构造函数并不是一个坏主意,它将对象构造为有效空状态。在构造过程中处理异常总是比在操作过程中处理异常更复杂。复杂的解决方案需要更多的脑力,而脑力在大型程序中是稀缺资源
所以总的来说,我认为你所说的一切都是正确的。我喜欢将对象视为具有 6 个不同的阶段。

  • 分配
  • 建设
  • 初始化
  • Active // 做有用的功能,等等
  • 破坏
  • 解除分配

对于简单的对象,将其简化为只是构造/破坏非常方便,并且减少了您必须考虑的事情。对于重量级的对象,将每个阶段分开并使其可单独测试是有意义的。您可以通过这种方式获得更好的错误处理和错误报告(恕我直言)

【讨论】:

    【解决方案2】:

    而不是使用:

    • std::shared_ptr&lt;&gt;
    • make_shared

    您也可以使用:

    • std::unique_ptr&lt;&gt;
    • make_unique

    shared_ptr 速度慢并且容易出现问题。

    在这种情况下,您需要使用std::unique_ptr&lt;&gt;,因为在存储指向另一个对象的指针时不需要它来获取对象的所有权。

    回想起来,您不需要共享所有权。

    【讨论】:

      【解决方案3】:

      我将首先为我的日志记录定义一个接口,这样我就可以通过多种方式创建记录器(您将需要它来进行单元测试)。然后通过const 引用捕获异常。为了能够使用接口的多态性,需要该接口的 std::unique_ptr(但至少它不是 shared_ptr)。

      完整示例:

      #include <iostream>
      #include <memory>
      #include <string>
      
      class log_itf
      {
      public:
          virtual void log(const std::string& str) const = 0; // logging should not modify anything
          virtual ~log_itf() = default;
      };
      
      class logger :
          public log_itf
      {
      public:
          logger(const std::string& /*filename*/)
          {
              // let this throw if it fails.
              // throw std::runtime_error("file not found");
          }
      
          void log(const std::string& str) const override
          {
              std::cout << str << "\n";
          }
      };
      
      // a logger that does nothing.
      class null_logger :
          public log_itf
      {
      public:
          void log(const std::string&) const override {}
      };
      
      // example of a class that wants to use logging
      class class_that_uses_logging
      {
      public:
          explicit class_that_uses_logging(const log_itf& logger) :
              m_logger{ logger }
          {
              m_logger.log("constructor of class that uses logging");
          }
      
      private:
          const log_itf& m_logger;
      };
      
      // helper factory methods that do the typecasting to interface for you
      std::unique_ptr<log_itf> create_logger(const std::string& filename)
      {
          return std::make_unique<logger>(filename);
      }
      
      std::unique_ptr<log_itf> create_null_logger()
      {
          return std::make_unique<null_logger>();
      }
      
      
      int main()
      {
          try
          {
              // for production code
              auto logger = create_logger("log.txt");
      
              // for test code
              // auto logger = create_null_logger();
      
              logger->log("Hello World\n");
      
              // or pass logging to some other class
              // this pattern has a name and is called dependency
              // injection and is very useful for big projects 
              // (with unit tests)
              class_that_uses_logging object(*logger);
          }
          catch (const std::exception& e) // always catch by const&
          {
              std::cout << "error : " << e.what() << "\n";
          }
      
          return 0;
      }
      

      【讨论】:

      • 你说得对,它不需要。更新
      猜你喜欢
      • 2021-10-13
      • 1970-01-01
      • 2020-08-30
      • 1970-01-01
      • 1970-01-01
      • 2021-09-08
      • 2011-09-29
      • 2016-12-17
      • 1970-01-01
      相关资源
      最近更新 更多