【问题标题】:Destructor called when objects are passed by value对象按值传递时调用的析构函数
【发布时间】:2014-09-22 07:40:10
【问题描述】:

即使对象是通过普通的方式传递给函数的 按值调用的参数传递机制,理论上可以保护 并隔离调用参数,它仍然是可能的一方 发生可能影响甚至损坏用作对象的对象的效果 争论。例如,如果用作参数的对象分配 内存并在销毁时释放该内存,然后释放其本地副本 当函数的析构函数为 叫。这将使原始对象损坏并有效 没用。

这是用 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;
}

在这个声明中没有发生错误。错误是否也应该在这里发生,因为它被引用了? 谁能解释一下这两种情况会发生什么。

【问题讨论】:

  • 你了解引用传递是什么意思吗?
  • 复制构造函数为何有用的好例子
  • @Claptrap 是一个很好的例子,说明为什么您不应该自己处理内存并让智能指针来处理。比三/五规则更好的是rule of zero
  • @JBL 在某些情况下无法使用智能指针,例如遗留代码或流程规则。
  • @Claptrap 当然,但由于 OP 不是在这种情况下而是在学习,让我们首先指向现代实践,如果需要出现,遗留黑客:)

标签: c++


【解决方案1】:

这确实是因为您没有提供复制构造函数。因此编译器会为你生成一个,它会进行简单的复制。这就是这里有问题的指针的简单副本。

以下声明

void SomeFunc(Sample x);

当你将s1传递给函数时确实会有一个副本,但是这个副本会有一个指针的副本,即两个对象会指向同一个int

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

那么,对于下面的声明

void SomeFunc(Sample &x);

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

【讨论】:

  • +1 好答案!永远值得记住Rule of 3
  • Rule of 0 如果可以避免 3(或 5,C++11 后)的规则会更好
【解决方案2】:

我将更多地从“尽可能避免使用原始指针”的现代 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;
     }
};

故事的寓意是,您可以让默认行为为您完成正确工作的次数越多……您编写的代码越少……出现错误的可能性就越小。因此,虽然了解复制构造函数的作用以及何时调用它们很好,但了解如何编写它们也同样重要!

请注意,unique_ptr 和 shared_ptr 来自 C++11,需要#include &lt;memory&gt;

【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-28
      • 1970-01-01
      • 2012-01-17
      • 2014-06-08
      • 2015-04-10
      • 1970-01-01
      • 2013-03-14
      • 2012-05-05
      相关资源
      最近更新 更多