鉴于代码:
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();
}
输出结果为:
Say i am in someFunc
Null pointer assignment(Run-time error)
我无法理解为什么第二行输出来了。输出的第二行。我认为编译器在未明确指定时提供了复制构造函数。因此,在函数SomeFunc(Sample x)中,应该创建和销毁SomeFunc()的本地对象,它是Sample类型的X,并且main()中的Sample类型对象(s1)应该保持不变,并且应该只在之后释放主要出口。请回答上述行为发生的原因?
答案 0 :(得分:6)
为什么上述行为正在发生?
简答:
因为您没有关注 Rule of Three 。
长答案:
你的类有一个指针成员ptr
,在构造函数中有动态内存分配,在析构函数中有释放,而你的代码在通过调用拷贝构造函数传递给函数SomeFunc()
时创建对象的临时副本,由编译器隐式生成,它会创建指针成员的 shallow copy 。一旦在函数调用结束时销毁了临时文件,就会在析构函数中释放内存并留下一个悬空指针。当你调用函数PrintVal()
导致未定义的行为时,这个无效指针会被进一步解除引用,以分段错误的形式出现。
如何避免此问题?
简答:
遵循三规则。
长答案:
您应该提供一个复制构造函数,用于创建指针成员ptr
的深层副本。这可以确保在成员中创建的对象的指针成员在整个程序的生命周期内保持有效。
修改强>
实际上,甚至在调用函数之前甚至可能会出现问题,特别是在调用函数时:
Sample s1= 10;
这会调用转换构造函数
Sample(int i)
创建一个临时Sample
对象,然后通过调用隐式复制构造函数将其用于构造s1
对象。如果是这种情况,创建的临时对象将在创建{{1将指针成员s1
置于悬空状态。
但是,大多数编译器将使用 Return Value Optimization(RVO) 通过复制省略来应用优化,从而无需调用复制构造函数&amp;因此这可能不是问题。
在任何一种情况下,解决问题的方法都是一样的。