【问题标题】:How to initialize member variable with super class type depending on constructor argument?如何根据构造函数参数初始化具有超类类型的成员变量?
【发布时间】:2018-05-30 15:17:47
【问题描述】:

搜索了几个小时后,我终于到了这里。我有一个 Container 类,其中有一个指向 Base 类的指针作为成员变量。这应该引用 Spec1 或 Base 的另一个继承类,我在这里省略了。类型应由构造函数中的参数确定(例如 string、enum、int 等)。

我阅读了很多关于动态内存分配以及为什么应尽可能避免使用它的信息。这里可以避免吗?在构造函数之后是否有任何常用对象被销毁?还是设计理念完全错误?我来自 Java :( 在此先感谢。

class Base{
  public:
    virtual ~Base(){}; // required?
    virtual void doSomething() = 0;
};

class Spec1 : public Base {
     public:
       Spec1(){};
       Spec1(int i){
         // whatever
       }
       void doSomething(){
          std::printf("hello world");
       }
};

class Container{
   public:
     Container(String type_message){
      if (type_message.compare("We need Spec1")){
          m_type = new Spec1(1);
      } // add more ifs for other types (Spec2, Spec3 etc.)
     }
     void doSomethingWithSpec(){
        m_type->doSomething();
     }
   private:
      Base* m_type;
 };

int main (int argc, char **argv){
    Container a ("We need Spec1");
    a.doSomething();
}

【问题讨论】:

  • 建议在您的情况下使用std::unique_ptrstd::make_unique,而不是原始指针和new
  • 您的设计总体上很好,尽管Container 缺少调用delete m_type; 的析构函数(并且还应该禁用默认的复制构造函数和复制赋值运算符,因为不应复制m_type原样)。是的,~Base 必须是virtual,所以delete m_type; 可以正常工作,调用您选择实例化的任何派生类型的析构函数。
  • 如果程序被 ctrl-c(ubuntu 终端)中止,是否会调用析构函数?还是我必须手动调用析构函数?
  • 关于“尽可能避免”动态分配,这是真的,但如果你想在 C++ 中实现多态性,那么你必须使用指针或引用,所以在这种情况下(即如果引用不合适)您将需要使用动态分配。

标签: c++ pointers inheritance member


【解决方案1】:

要求Container 了解Base 的每个可能的派生类听起来不像是好的设计。这就是工厂函数的用途。

Container 将该对象存储为std::unique_ptr 以避免内存泄漏和手动内存管理。

struct Base {
    virtual ~Base() = default;
    virtual void doSomething() = 0;
};

struct Spec1 : Base {
    void doSomething() override {
        std::printf("%s\n", __PRETTY_FUNCTION__);
    }
};

// Factory function.
std::unique_ptr<Base> createBase(std::string const& type) {
    if(type == "Spec1")
        return std::unique_ptr<Base>(new Spec1);
    throw std::runtime_error("Unknown type " + type);
}

class Container {
    std::unique_ptr<Base> m_type;
public:
    Container(std::string const& type)
        : m_type(createBase(type))
    {}

    void doSomething(){
        m_type->doSomething();
    }
};

int main() {
    Container a ("Spec1");
    a.doSomething();
}

【讨论】:

  • 感谢工厂功能,以前不知道。但实际上我想保持简短和简单,并且我之前在构造函数中做了其他事情,所以这些都会传递给 createBase 方法。
  • @Lucker10 ContainerBase 看起来像 strategy design pattern
【解决方案2】:

构造函数的工作方式与任何其他函数一样。局部变量在堆栈中分配并在函数完成时超出范围,而动态内存分配在堆栈中完成并且需要在函数完成之前显式释放(在 C++ 中而不是在 Java 中)。

但是在你的情况下

m_type = new Spec1(1);

new Spec1(1) 在堆中动态分配,不会在构造函数代码完成时被销毁。引用存储在类的成员变量中。所以只要Container类的实例在作用域内,分配给Spec1(1)的内存就可以被引用。

比较只考虑其他情况。

Container(String type_message){
      Base* m_type;
      if (type_message.compare("We need Spec1")){
          m_type = new Spec1(1);
      } // add more ifs for other types (Spec2, Spec3 etc.)
     }

在这里,只要构造函数完成,m_type 就会超出范围,但 new Spec1(1) 仍会因为内存泄漏而在堆中。

【讨论】:

  • 所以没有可能避免使用“新”?这是“新”的典型用例吗?
  • 这里的 new 类似于 Java 中的 new。您可以通过不将基类对象声明为指针来避免 new。 Base m_type; m_type = Spec1(1) 尽量避免动态分配。 stackoverflow.com/questions/22146094/… 很好读
  • 这不适用于虚拟课程:error: cannot declare field ‘Container::m_type’ to be of abstract type ‘Base’ Base m_type;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多