【问题标题】:Is this bad RAII design?这是糟糕的 RAII 设计吗?
【发布时间】:2012-09-06 20:02:53
【问题描述】:

我来自 Java 背景,但后来我学习了 C++,并且已经使用它编程了几年(主要是调试和编写修复程序,而不是从头开始设计程序)。但是,我今天遇到了一个问题,坦率地说,我有点惊讶花了这么长时间才遇到它。

假设我有一个名为 Class1 的类,其头文件包含(以及其他代码):

class Class1 {
    private:
        Class2 object;
}

Class2 类没有指定默认构造函数。现在,在 Class1 构造函数中,我正在读取文件的二进制标头并使用从中解析的信息来初始化 Class2,如下面的伪代码所示:

Class1::Class1(std::string) {
    // Read some binary info from a file here

    // Parse that binary info

    object2 = Class2(info);

在 Java 中,因为它不使用 RAII 范式,所以这是完全合法的。但是,由于 C++ 使用 RAII,所以在我执行 object2 = Class2(info); 时,对象 2 已经使用其默认构造函数进行了初始化。我最初无法调用该构造函数(在 Class1 头文件中),因为我还没有创建 object 所需的信息。但是,我不能只将 object2 设置为构造函数的本地,因为我需要其他函数才能看到/使用它。

显然这行不通。这些东西的标准方法是什么?我实际上想过只是将 Class1 更改为拥有一个 Class2 指针,如下所示:

class Class1 {
    private:
        Class2* objectPointer;
}

然后调用*objectPointer = Class2(info)。但是,在我的情况下,“Class2”是一个 ifstream,似乎 operator= 函数已被删除,并且不适用于任何一种方法。

那么...我该怎么做呢?

【问题讨论】:

  • 哈哈!
  • 我认为说“C++ 使用 RAII”不太正确。 C++ 启用 RAII,但不需要使用它。
  • 请注意,RAII 是一个很棒的功能的可怕名称。虽然名称暗示了初始化,但重要的方面是破坏:每个资源都应该由一个类管理。它不支持/反对 2 阶段初始化(尽管您可能希望尽可能避免它)
  • @RobK 是的,我可能应该更仔细地措辞我的问题......类似于“但是,因为 C++ 允许 RAII 并且它通常是很好的做法”或类似的东西。

标签: c++ constructor raii


【解决方案1】:

我建议使用本地函数来完成读取二进制信息并直接在构造函数列表中初始化的位:

namespace {

InfoObject readInfo(std::string s)
{
// Read some binary info from a file here

// Parse that binary info
  return info;
}

}

Class1::Class1(std::string s)
: object(readInfo(s))
{
}

当然,使用指针也是一种选择。这也是为什么这在 Java 中更自然地工作的原因(每个用户类型在内部都是一个指针)。不过,您可能想使用智能指针。

【讨论】:

    【解决方案2】:

    如果“读取和解析”部分不需要构建对象,那么您可以将其移至静态(或非成员)函数,并使用其结果初始化成员:

    Class1::Class1(std::string) :
        object2(read_class2_info(some_file))
    {}
    

    如果由于某种原因确实无法将文件读取与对象构造分开,并且以后无法重新分配object2,那么您将需要使用指针。要创建对象,您可以使用 new:

    objectPointer = new Class2(info);
    

    但是,为了避免自己弄乱Rule of Three,您应该避免自己管理动态对象,而是使用智能指针:

    std::unique_ptr<Class2> objectPointer;
    
    objectPointer.reset(new Class2(info));
    

    【讨论】:

      【解决方案3】:

      因为您的object 不是const,这完全合法。但是,如果您想在初始化阶段初始化objects,则必须提供信息。你可以这样做

       Class1::Class1(std::string file_name) : object(InfoFromFile(file_name)) {}
      

      其中InfoFromFile() 可以是独立函数(在 .cc 文件中的匿名命名空间中声明)或Class1 的静态成员函数。如果生成 Class2 所需的信息需要比 file_name 更多的信息,您可以将其提供给该函数。

      【讨论】:

      • 谢谢!我完全忘记了使用初始化列表的一个重要点是选择为该成员调用哪个构造函数而不是默认构造函数。我像你提到的那样在列表中添加了object,果然,现在一切都很好。
      【解决方案4】:

      我认为您必须使用虚拟数据初始化 object,然后在解析后使用新数据对其进行更新。

      类似这样的:

      Class1(std::string str)
       : object("some valid-but-meaningless data")
      {
          // parse info
          object = Class2(info);
      

      如果这不好,您可能必须在静态因子方法中进行解析,然后将信息传递给构造函数(您很可能将其设为私有)。像这样的:

      Class1(Info info)
       : object(info)
      {
          // whatever else you want
      }
      
      static Class1 create(std::string)
      {
          // parse info
          return Class1(info);
      }
      

      编辑:我实际上更喜欢沃尔特的回答;在初始化中使用函数是个好主意。我只是把这个留在这里,你可以考虑一些替代的想法。

      【讨论】:

        猜你喜欢
        • 2011-08-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-22
        相关资源
        最近更新 更多