2

我写了一个测试来检查在堆栈变量上覆盖赋值之前是否调用了析构函数,但我找不到任何合理的结果解释......

这是我的测试(在 Visual C++ 2008 发布模式下):

#include <iostream>
class C {
public:
 char* ptr;
 C(char p) { ptr = new char[100]; ptr[0] = p;}
 ~C() { std::cout << ptr[0] << ' '; delete [] ptr; }
};

int _tmain(int argc, _TCHAR* argv[])
{
 {
  C s('a');
  s = C('b');
  s = C('c');
  s = C('d');
 }
 std::cin.get();
 return 0;
}

如果我的假设是正确的,我希望得到“abcd”,如果错误,我期望得到“d”。相反,我得到“bcdx”。“x”根据分配给 ptr 的内存量而变化,表明它正在读取随机堆值。

我相信正在发生的事情(如果我错了,请纠正我)是每个构造函数调用都会创建一个新的堆栈值(让我们称它们为 s1、s2、s3、s4),然后分配让 s1.ptr 被 s4.ptr 覆盖. 然后 s4 在复制后立即被销毁,但 s1(带有悬空的 ptr)在离开范围时被销毁,导致 s4.ptr 的双重删除,而原始 s1.ptr 没有删除。

有什么办法可以解决不涉及使用 shared_ptrs 的这种无益行为?

编辑:将“删除”替换为“删除 []”

4

7 回答 7

11

三法则

您的应用程序行为未定义,因为如前所述,多个对象将共享对公共指针的访问权并尝试读取它...

三项规则规定,每次您定义以下之一时:

  • 复制构造函数
  • 赋值运算符
  • 析构函数

然后您应该定义另一个,因为您的对象具有默认生成的方法不知道的特定行为。

编辑特殊例外
有时您只定义析构函数是因为您希望它是虚拟的,或者因为它记录了某些内容,而不是因为您的属性有一些特殊处理;)

于 2009-10-07T12:26:03.187 回答
3

由于您在析构函数中打印,因此 a 实例将在作用域的末尾(您看到的 x )被删除。

分配完成后,其他实例将被删除。这解释了bcdx。

下次使用

delete [] ptr; 

而不是删除

于 2009-10-07T12:26:04.920 回答
2

添加其他编译器定义的方法:

class C
{
    public:
      char* ptr;
      C(char p)                { ptr = new char[100]; ptr[0] = p;}
     ~C()                      { std::cout << ptr[0] << ' '; delete [] ptr; }
      C(C const& c)            { ptr = new char[100]; ptr[0] = c.ptr[0];}
      C& operator=(C const& c) { ptr[0] = c.ptr[0]; return *this;}
};

int _tmain(int argc, _TCHAR* argv[])
{
  {
      C s('a');
      s = C('b');
      s = C('c');
      s = C('d');
  }
  std::cin.get();
  return 0;
}

它现在应该打印出来:

bcdd

每个临时的都在表达式结束时被销毁。然后 s 最后被销毁(在将 'd' 复制到 ptr[0] 之后)。如果您在每种方法中添加一个打印语句,则更容易看到正在发生的事情:

>>           C s('a');
Construct 'a'

>>           s = C('b');
Construct 'b'  
Assign 'b' onto 'a'  
Destroy 'b'         (Temporary destroyed at ';')  

>>          s = C('c');
Construct 'c'  
Assign 'c' onto 'b' (was 'a' but has already been assigned over)  
Destroy 'c'         (Temporary destroyed at ';')

>>          s = C('d');  
Construct 'd'  
Assign 'd' onto 'c'  
Destroy 'd'         (Temporary destroyed at ';')  

>> End of scope.
Destroy 'd'         (Object s destroyed at '}')  

由于编译器定义了 4 种方法,因此适用“四规则”。
如果您的类包含该类拥有的 RAW 指针(拥有意味着您的对象决定了生命周期)。然后您必须覆盖所有 4 个编译器生成的方法。

由于您创建和销毁成员“ptr”,因此这是一个拥有的ptr。因此,必须定义所有四种方法。

于 2009-10-07T12:42:02.227 回答
1

您可以创建一个复制构造函数和赋值运算符,就像对任何拥有原始指针的类型一样。

于 2009-10-07T12:24:38.200 回答
1

s 只有在超出范围时才会被破坏 - 并且,正如您所提到的,它会在程序的执行过程中被覆盖,因此初始分配被泄露,最后一个被双重删除。

解决方案是重载赋值运算符(并且,正如 Pete 建议的那样,在它们齐头并进时提供一个复制构造函数),您将在其中清理您拥有的数组并复制给您的数组。

于 2009-10-07T12:25:59.117 回答
1

问题是您需要复制构造函数和赋值运算符。由于您将一个类分配给另一个类的行,因此制作了一个浅拷贝。这将导致两个类具有相同的 ptr 指针。如果其中一个被删除,则另一个指向顶部已释放内存

于 2009-10-07T12:28:57.723 回答
0

您尚未定义赋值或复制运算符。所以发生的事情是这样的:

C s('a');

's' 实例被创建并使用 'a' 初始化。

s = C('b');

这将创建一个临时对象,用“b”对其进行初始化,然后默认赋值运算符开始按位复制所有变量,覆盖 s 的 ptr。临时对象被销毁。发出 'b' 并删除 'ptr' (在 s 中渲染 ptr 无效)。

s = C('c');
s = C('d');

又是一样。临时对象被创建,用'c'初始化,s中的'ptr'被临时对象中分配的ptr覆盖。临时对象被销毁,发出 'c' 并使 s 中的 ptr 无效。重复 d。

  return 0;
}

最后 s 离开作用域,它的析构函数尝试发出 ptr 的第一个字符,但那是垃圾,因为 ptr 是由最后一个 ('d') 临时对象分配和删除的。删除 ptr 的尝试应该会失败,因为该内存已被删除。

要解决这个问题?定义显式复制构造函数和赋值运算符。

class C {
  // other stuff
  C(const C&rhs); // copy constructor
  C& operator=(const c& rhs){ // assignment operator
    a[0] = rhs.a[0];
    return *this;
  }
};
于 2009-10-07T12:37:25.573 回答