复制构造函数:何时释放存储?

Abh*_*hay 1 c++ copy-constructor

鉴于代码:

class Sample
{
 public:
      int *ptr;
      Sample(int i)
      {
      ptr = new int(i);
      }
      ~Sample()
      {
      delete ptr;
      }
      void PrintVal()
      {
      cout << "The value is " << *ptr;
      }
 };
 void SomeFunc(Sample x)
 {
     cout << "Say i am in someFunc " << endl;
 }
 int main()
 {
     Sample s1= 10;
     SomeFunc(s1);
     s1.PrintVal();
 }
Run Code Online (Sandbox Code Playgroud)

输出是:

Say i am in someFunc
Null pointer assignment(Run-time error)
Run Code Online (Sandbox Code Playgroud)

我无法理解为什么第二行输出来了.输出的第二行.我认为编译器在未明确指定时提供了复制构造函数.因此,在函数SomeFunc(Sample x)中,应该创建和销毁SomeFunc()的本地对象,它是Sample类型的X,并且main()中的Sample类型对象(s1)应该保持不变,并且应该只在之后释放主要出口.请回答上述行为发生的原因?

Alo*_*ave 6

为什么上述行为正在发生?

简答:
因为你没有遵守三法则.

龙答:
你的类有一个指针成员ptr在析构函数的构造和释放动态内存分配,而你的代码创建该对象的临时副本,同时传递给函数SomeFunc()调用拷贝构造函数,编译器隐式生成,它创建了一个浅拷贝的指针成员.一旦临时在函数调用结束时被销毁,内存将在析构函数中释放,并且你会留下一个悬空指针.当你调用函数PrintVal()导致一个未定义的行为时,这个无效的指针被进一步解除引用,它以一种形式表现出来.分段故障.

如何避免这个问题?

简答:
遵循三法则.

长答案:
您应该提供一个复制构造函数,用于创建指针成员的深层副本ptr.这确保了在成员中创建的对象的指针成员在整个程序的生命周期内保持有效.

编辑:
实际上,甚至在调用函数之前甚至可能会出现问题,特别是在调用时:

Sample s1= 10;
Run Code Online (Sandbox Code Playgroud)

这会调用转换构造函数,

 Sample(int i)
Run Code Online (Sandbox Code Playgroud)

创建一个临时Sample对象,然后s1通过调用隐式复制构造函数将其用于构造对象.如果是这种情况,创建的临时对象将s1在指针成员ptr处于悬空状态后被销毁.

但是,大多数编译器将使用返回值优化(RVO)通过复制省略来应用优化,从而无需调用复制构造函数,因此这可能不是问题.

无论哪种情况,问题的解决方案都保持不变.