即使遵循三条规则,C ++也会立即调用析构函数

时间:2015-03-18 21:04:33

标签: c++ windows destructor

以下是我的以下代码。一旦A的构造函数完成,它立即调用~B并删除分配的变量。我有复制构造函数和赋值构造函数。我应该实施五法则来防止这种情况吗?

编辑:我编辑了我的副本和赋值构造函数实现,但仍然在调用析构函数。

class B
{
public:
    C **table;
B() 
{
   table = new C *[TABLE_SIZE]();
}
B(const B& other)
{
   table = new C *[TABLE_SIZE];
   memcpy(table, other.table, sizeof(C *)* TABLE_SIZE);
}
B& operator = (const B& other)
{
  if (this == &other)
  {
     return *this;
  }
  delete[] table;
  table = new C *[TABLE_SIZE];
  memcpy(table, other.table, sizeof(C *)* TABLE_SIZE);
  return *this;
}
~B() 
{
    delete[] table;
}
}

class A
{
protected:
   B funcA();
private:
   B _b; 
}

A::A()
{
   this->_b = this->funcA();
   // calls ~B here and destroys table
}

3 个答案:

答案 0 :(得分:3)

你确实实现了复制构造函数和复制赋值运算符,但是错误。你实现了它们以完全执行默认操作,这对于你拥有动态分配的内存的情况当然是错误的存储在原始指针中。当然,规则三意味着您必须始终如一地实施

在您的情况下,这很可能意味着复制操作必须深层复制C对象:

class B
{
public:
  C *table;

  B() : table(new C()) {}

  B(const B& other) : table(new C(*other.table)) {}

  B& operator = (const B& other)
  {
    if (this == &other)
    {
        return *this;
    }
    delete table;
    table = new C(*other.table);
    return *this;
  }

  ~B() 
  {
    delete table;
  }
};

请注意,默认构造函数甚至无法编译 - 您正在为C*分配C**。我将上面的代码更改为使用单个分配(和单项delete)而不是数组。如果你有阵列,原则保持不变。

当然,如果可能的话,最好遵循零规则而不是三规则使用适当的智能指针。如果您的真实用例一个数组,则会将table转换为std::vector<C>。那么你根本不需要提供复制操作或析构函数。

如果您确实希望在C的所有副本中共享一个B,那么您将需要使用适当的共享所有权智能指针,例如std::shared_ptr<C>

答案 1 :(得分:0)

简单地说,funcA返回的临时实例应该被销毁,这是正常的和预期的。您的代码无法正常运行这一事实意味着您的复制构造函数和赋值运算符无法正常运行。

但更重要的是,你违反了几条比三条规则更重要的规则。您未能重复使用std::vector的现有解决方案。你通过将内存管理和B在家里做的其他事情联系在一起来违反SRP。您未能使用RAII来管理内部资源 - 一个很大的例外安全失败。

基本上,核心问题是你甚至试图执行这些操作。别。只需使用std::vector<C*>并让编译器和标准库供应商为您实现它们。它们将为您节省大量代码,而且,它们将是正确的,我会猜测您还不知道存在,例如异常安全。 “三规则”和“五规则”(甚至根本不存在足以成为规则)都远远低于“零规则”。

这是B但正确实施:

class B {
public:
    std::vector<C*> table;
    B() : table(TABLE_SIZE) {}
};

简单,不是吗?

作为旁注,我不知道你在哪里得到你的学习材料,但是你应该点燃任何来源如此严重过时,以推荐自我分配检查,以及任何该代码。也可以告诉你,640k的内存是你所需要的。

答案 2 :(得分:0)

你的构造函数是:

A::A()
{
    this->_b = this->funcA();
    // calls ~B here and destroys table
}

评论是正确的,尽管被销毁的表是临时对象中的表,而不是_b的表。由于您修复了复制赋值运算符,这意味着_b的表格没问题。

执行此构造函数的步骤列表如下:

  1. 调用_b
  2. 的默认构造函数
  3. 调用funcA,返回B
  4. 类型的临时对象
  5. 使用该临时对象调用_b的赋值运算符
  6. 销毁临时对象。
  7. 可以通过直接提供funcA()作为_b的初始化程序来改进构造函数,而不是首先使用其赋值运算符构造_b。 (但是仍然存在临时对象破坏)。

    根据您的编译器设置,在从函数返回的过程中可能还会创建和销毁另一个临时对象。 (通常不存在)。