我有以下代码来测试我对C ++中基本指针的理解:
// Integer.cpp
#include "Integer.h"
Integer::Integer()
{
value = new int;
*value = 0;
}
Integer::Integer( int intVal )
{
value = new int;
*value = intVal;
}
Integer::~Integer()
{
delete value;
}
Integer::Integer(const Integer &rhInt)
{
value = new int;
*value = *rhInt.value;
}
int Integer::getInteger() const
{
return *value;
}
void Integer::setInteger( int newInteger )
{
*value = newInteger;
}
Integer& Integer::operator=( const Integer& rhInt )
{
*value = *rhInt.value;
return *this;
}
// IntegerTest.cpp
#include <iostream>
#include <cstdlib>
#include "Integer.h"
using namespace std;
void displayInteger( char* str, Integer intObj )
{
cout << str << " is " << intObj.getInteger() << endl;
}
int main( int argc, char* argv[] )
{
Integer intVal1;
Integer intVal2(10);
displayInteger( "intVal1", intVal1 );
displayInteger( "intVal2", intVal2 );
intVal1 = intVal2;
displayInteger( "intVal1", intVal1 );
return EXIT_SUCCESS;
}
此代码完全按预期工作,打印出来:
intVal1 is 0
intVal2 is 10
intVal1 is 10
但是,如果我删除了复制构造函数,它会输出如下内容:
intVal1 is 0
intVal2 is 10
intVal1 is 6705152
我不明白为什么会这样。我的理解是,当赋值给不存在的对象时,使用复制构造函数。这里intVal1
确实存在,为什么不调用赋值运算符?
答案 0 :(得分:19)
在分配期间不使用复制构造函数。在将参数传递给displayInteger
函数时,将使用您的案例中的复制构造函数。第二个参数按值传递,这意味着它由复制构造函数初始化。
您的复制构造函数版本执行深度复制类所拥有的数据(就像您的赋值运算符一样)。因此,一切都可以正常使用您的版本的复制构造函数。
如果删除自己的复制构造函数,编译器将隐式生成一个。编译器生成的复制构造函数将执行对象的浅复制。这将违反"Rule of Three"并破坏您班级的功能,这正是您在实验中观察到的。基本上,第一次调用displayInteger
会损坏您的intVal1
对象,第二次调用displayInteger
会损坏您的intVal2
个对象。之后,两个对象都被破坏,这就是第三个displayInteger
调用显示垃圾的原因。
如果您将displayInteger
的声明更改为
void displayInteger( char* str, const Integer &intObj )
即使没有明确的复制构造函数,您的代码也会“正常工作”。但在任何情况下忽略"Rule of Three"都不是一个好主意。以这种方式实施的课程必须遵守“三规则”或必须是不可复制的。
答案 1 :(得分:4)
您遇到的问题是由默认的复制构造函数引起的,它会复制指针但不会将其与新分配的内存相关联(就像复制构造函数的实现一样)。按值传递对象时,会创建一个副本,当执行超出范围时,将复制此副本。析构函数中的delete
使value
对象的intVal1
指针无效,使其悬空指针,解除引用 未定义的行为 强>
调试输出可能用于理解代码的行为:
class Integer {
public:
Integer() {
cout << "ctor" << endl;
value = new int;
*value = 0;
}
~Integer() {
cout << "destructor" << endl;
delete value;
}
Integer(int intVal) {
cout << "ctor(int)" << endl;
value = new int;
*value = intVal;
}
Integer(const Integer &rhInt) {
cout << "copy ctor" << endl;
value = new int;
*value = *rhInt.value;
}
Integer& operator=(const Integer& rhInt){
cout << "assignment" << endl;
*value = *rhInt.value;
return *this;
}
int *value;
};
void foo(Integer intObj) {
cout << intObj.value << " " << *(intObj.value) << endl;
}
现在输出此代码:
Integer intVal1;
Integer intVal2(10);
foo( intVal1 );
foo( intVal2 );
intVal1 = intVal2;
foo( intVal1 );
是:
构造函数
构造函数(INT)
复制ctor
0x9ed4028 0
的析强>
复制ctor
0x9ed4038 10
析构函数
分配
复制ctor
0x9ed4048 10
析构函数
析构函数
析构函数
表示在按值传递对象时使用复制构造函数。但是,重要的是要注意这里的析构函数是从函数返回的。如果删除了复制构造函数的实现,则输出为:
构造函数
构造函数(INT)
0x8134008 0
析构函数
0x8134018 10
析构函数
分配
0x8134008 135479296
析构函数
析构函数
析构函数
显示第一个副本名为delete
在同一个指针(指向0x8134008
)上,后来被第三个副本使用,其中使用了这个悬空指针所指向的内存。
答案 2 :(得分:2)
想想这个电话:
displayInteger( "intVal1", intVal1 );
您正在intVal1
的{{1}}参数中创建intObj
的副本:
displayInteger
该副本将指向void displayInteger( char* str, Integer intObj )
{
cout << str << " is " << intObj.getInteger() << endl;
}
所在的int
。当intVal1
返回时,displayInteger
将被销毁,这将导致intObj
被销毁,int
中的指针将指向无效对象。此时,如果您尝试访问该值,则所有投注均已关闭(A.K.A.未定义的行为)。类似的事情发生在intVal1
。
在更一般的层面上,通过删除复制构造函数,您违反了规则三,这通常会导致这些问题。