即使通过法线将对象传递给函数 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;
}
在此声明中不会发生错误。不应该在这里发生错误,因为它被引用了吗? 任何人都可以解释在这两种情况下会发生什么。
答案 0 :(得分:10)
这确实是因为您没有提供复制构造函数。因此,编译器将为您生成一个,这将执行简单的复制。而这是指针的微不足道的副本,这里有问题。
以下声明
void SomeFunc(Sample x);
当您将s1
传递给函数时,确实会有一个副本,但此副本将具有指针的副本,即两个对象将指向相同的int
。 / p>
然后,当退出该函数时,副本将被销毁并将删除该指针,将原始对象保留在调用代码中,并且指针刚刚删除(记住,它们指向相同的东西)。
然后,对于以下声明
void SomeFunc(Sample &x);
你没有任何副本,因此问题不会出现。实际上,通过引用传递意味着在函数内部,您操作的Sample
对象与传递给函数的对象完全相同,并且在函数退出时不会被销毁。
答案 1 :(得分:3)
我将从Modern C ++的角度给出更多答案,“如果可以,请避免使用原始指针”。但我还要指出你应该注意的一个重要区别:
但首先,让我们考虑你的意图。如果我写道:
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
- s1
和x
的实例(但只有x
可访问),x.ptr
的值等于s1.ptr
。然后当SomeFunc
结束时,析构函数被调用,从那一点s1.ptr
指向未分配的内存。它被称为“悬空指针”。