按值传递对象时调用的析构函数
Destructor called when objects are passed 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;
}
在这个声明中没有出现错误。错误是否也应该出现在这里,因为它被引用了?有人能解释一下这两种情况下发生了什么吗。
这确实是因为您没有提供复制构造函数。因此,编译器将为您生成一个,用于执行琐碎的复制。这是指针的琐碎副本,在这里有问题。
以下申报
void SomeFunc(Sample x);
当您将s1
传递给函数时,确实会有一个副本,但这个副本将有指针的副本,也就是说,两个对象将指向同一个int
。
然后,当退出函数时,副本将被销毁并删除该指针,在调用代码中留下一个刚刚删除的指针的原始对象(记住,它们指向相同的东西)。
然后,对于以下声明
void SomeFunc(Sample &x);
你没有任何副本,所以问题没有出现。事实上,通过引用传递意味着在函数内部,您正在操作的Sample
对象与您传递给函数的对象完全相同,并且在函数退出时不会被销毁。
我将从现代C++的角度给出更多的答案,即"如果可以的话,避免原始指针"。但我也要指出一个你应该注意的重要区别:
C++构造函数语法
但首先,让我们考虑一下你的意图。如果我写:
Sample x = 1;
Sample y = x;
语义应该是什么?
Sample
"副本"是否应该每个都有自己独立的"ptr",其指向对象的生存期只与它们所在的类一样长?
如果这是你想要的,通常情况下你根本不需要指针。
大多数情况下,类的总大小将足够合理,如果您在没有new
的情况下声明它们(就像您在这里一样),则堆栈分配不会成为问题。那么,为什么要涉及指针呢?只需使用int i
(或任何非POD类)。
如果您确实有这样的情况,确实需要动态分配大块数据来管理自己(相对于延迟到C++库集合或类似的集合),那么这些数据可能会超出您的堆栈。如果您需要动态分配,您将需要以某种方式复制构造。这意味着Sample
将需要显式管理复制构造,或者使用一个智能指针类来对其进行精细处理,这样它就不必了。
首先假设您保留原始指针,这意味着:
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
也通过引用获取其值。我还将提到查看初始值设定项列表与赋值。
(注意:实际上,复制名为clone_ptr
的智能指针类的模式有一个名称,所以您甚至不必编写复制构造函数。它不在标准C++库中,但您可以在周围找到实现。)
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>
。
您的对象是逐字段复制的,因此在SomeFunc
中有两个实例Sample
-s1
和x
(但只有x
可访问),x.ptr
的值等于s1.ptr
。然后,当SomeFunc
结束时,将调用析构函数,并从那时起s1.ptr
指向未分配的内存。它被称为"悬空指针"。
- 析构函数调用
- 在具有向量的类构造函数中进行析构函数调用
- 从 c++ 中派生类的析构函数调用虚函数
- C++析构函数调用两次,堆栈分配的复合对象
- C++ 在析构函数调用之前删除的动态成员数组
- 析构函数调用c++中的一个向量
- Singleton模式中的手动析构函数调用:调用多次
- 从内部类的析构函数调用虚拟函数
- 与 boost odeint 集成期间的析构函数调用
- 堆栈展开如何与析构函数调用有关?
- C++:优化析构函数调用
- 以逗号分隔的表达式中的析构函数调用
- GCC 9.1 返回 void& 作为显式析构函数调用的结果类型。这是一个错误吗?
- 从C++中的虚拟析构函数调用虚拟方法
- 从指针返回对象时出现意外的析构函数调用
- 使用 decltype 显式析构函数调用
- C++析构函数调用了错误的对象
- 了解虚拟函数和析构函数调用
- 多重继承析构函数调用他自己和父析构函数?c++
- 析构函数调用表单不适当的库