对象按值传递时调用的析构函数

时间:2014-09-22 07:40:10

标签: c++

  

即使通过法线将对象传递给函数   call-by-value参数传递机制,理论上可以保护   并且使调用参数绝缘,对于一方来说仍然是可能的   可能影响甚至损坏用作物体的物体   论点。例如,如果用作参数的对象分配   内存并在销毁时释放内存,然后释放其本地副本   函数内部将在其析构函数时释放相同的内存   调用。这将使原始对象受损且有效   无用的。

这是用C ++编写的:完整参考

在这个程序中

#include<iostream>

using namespace std;

class Sample
{         
 public:
         int *ptr;
         Sample(int i)
         {
         ptr = new int(i);
         }
         ~Sample()
         {
         cout<<"destroyed";
         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();
}

当对象s1从对象返回时被销毁,它会生成运行时错误。我无法弄清楚为什么会发生这种情况,因为应该制作副本。 我想也许是因为班级定义中没有复制构造函数。但我很惊讶地发现,如果使用这个函数声明

 void SomeFunc(Sample &x)
{
 cout << "Say i am in someFunc " << endl;
}

在此声明中不会发生错误。不应该在这里发生错误,因为它被引用了吗? 任何人都可以解释在这两种情况下会发生什么。

3 个答案:

答案 0 :(得分:10)

这确实是因为您没有提供复制构造函数。因此,编译器将为您生成一个,这将执行简单的复制。而这是指针的微不足道的副本,这里有问题。

以下声明

void SomeFunc(Sample x);

当您将s1传递给函数时,确实会有一个副本,但此副本将具有指针的副本,即两个对象将指向相同的int。 / p>

然后,当退出该函数时,副本将被销毁并将删除该指针,将原始对象保留在调用代码中,并且指针刚刚删除(记住,它们指向相同的东西)。

然后,对于以下声明

void SomeFunc(Sample &x);

你没有任何副本,因此问题不会出现。实际上,通过引用传递意味着在函数内部,您操作的Sample对象与传递给函数的对象完全相同,并且在函数退出时不会被销毁。

答案 1 :(得分:3)

我将从Modern C ++的角度给出更多答案,“如果可以,请避免使用原始指针”。但我还要指出你应该注意的一个重要区别:

C++ constructor syntax

但首先,让我们考虑你的意图。如果我写道:

Sample x = 1;
Sample y = x;

语义应该是什么?

  

每个Sample“副本”是否都有自己独立的'ptr',其指向对象的生命周期只与他们所居住的类一样长?

通常情况下,如果这是你想要的,你根本就不需要指针。

大多数情况下,如果你在没有new (就像你在这里)的情况下声明它们,那么总类的大小将足够合理,以至于堆栈分配不会成为问题。那么为什么要涉及指针呢?只需使用int i(或任何非POD类)。

如果你实际上有的情况需要动态分配大块数据来管理自己(与推迟到C ++库集合或类似)那些可能exceed your stack。如果您需要动态分配,那么您将需要以某种方式进行复制构建。这意味着Sample将需要明确管理复制构造 - 或者 - 使用一个smart pointer类来实现它,因此它不必

首先让我们说你保留了原始指针,这意味着:

Sample(const Sample & other)
{
   ptr = new int(*other.ptr);
}

您可以使用unique_ptr来减少此情况下出错的可能性。 unique_ptr将销毁其析构函数运行时自动保持的原始指针所指向的数据。所以你不必担心调用delete

同样,unique_ptr默认会拒绝复制。因此,如果你刚写了:

class Sample
{         
public:
     unique_ptr<int> ptr;
     Sample(int i)
     {
         ptr = std::unique_ptr<int>(new int(i));
     }
     ~Sample()
     {
        cout << "destroyed";
     }
     void PrintVal()
     {
         cout << "The value is " << *ptr;
     }
};

课程本身可以构建,但你会在你的网站上遇到错误。他们会指出你正在为复制结构尚未正确定义的东西制作副本。不仅如此......您不只是在程序中制作一个副本,而且两个

In function ‘int main()’:
error: use of deleted function ‘Sample::Sample(const Sample&)’
Sample s1 = 10;
            ^
note: ‘Sample::Sample(const Sample&)’ is implicitly deleted
       because the default definition would be ill-formed:

error: use of deleted function ‘Sample::Sample(const Sample&)’
SomeFunc(s1);
           ^

这使您可以添加一个等效于:

的复制构造函数
     Sample(const Sample & other)
     {
         ptr = std::unique_ptr<int>(new int(*other.ptr));
     }

另外,您可能希望将Sample s1 = 10;更改为Sample s1 (10);以避免副本存在。就此而言,您可能希望SomeFunc也可以通过引用来获取其值。我还要提到initializer lists vs assignments

(注意:实际上有一个名为clone_ptr的智能指针类模式的名称,因此您甚至不必编写那个复制构造函数。它不在标准C ++库中,而是you'll find implementations around。)

  

Sample“副本”是否应共享仅在最后一次引用消失后删除的公共动态ptr

使用智能指针更容易,而Sample上根本不需要复制构造函数。使用shared_ptr。 shared_ptr的默认行为是能够使用简单的赋值进行复制。

class Sample
{         
public:
     shared_ptr<int> ptr;
     Sample(int i)
     {
         ptr = make_shared<int>(i);
     }
     ~Sample()
     {
        cout << "destroyed";
     }
     void PrintVal()
     {
         cout << "The value is " << *ptr;
     }
};

故事的道德是,你可以让默认行为为你做正确的工作越多......你写的代码越少......你对bug的潜力就越小。因此,虽然知道复制构造函数的作用以及何时调用它们是很好的,但知道如何编写它们同样重要!

请注意,unique_ptr和shared_ptr来自C ++ 11,需要#include <memory>

答案 2 :(得分:0)

您的对象已逐字段复制,因此在SomeFunc内,您有两个Sample - s1x的实例(但只有x可访问),x.ptr的值等于s1.ptr。然后当SomeFunc结束时,析构函数被调用,从那一点s1.ptr指向未分配的内存。它被称为“悬空指针”。