[英]assignment operator vs. copy constructor C++
我有以下代码来测试我对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
确实存在,那么为什么不调用赋值运算符?
在分配期间不使用复制构造函数。 在将参数传递给displayInteger
函数时,将使用您的案例中的复制构造函数。 第二个参数按值传递,这意味着它由复制构造函数初始化。
您的复制构造函数版本对类所拥有的数据执行深度复制(就像您的赋值运算符一样)。 因此,一切都适用于您的版本的复制构造函数。
如果删除自己的复制构造函数,编译器将隐式生成一个。 编译器生成的复制构造函数将执行对象的浅复制。 这将违反“三法则”并破坏您班级的功能,这正是您在实验中观察到的。 基本上,第一次调用displayInteger
损坏你的intVal1
对象,第二次调用displayInteger
损坏你的intVal2
对象。 之后,两个对象都被破坏,这就是第三个displayInteger
调用显示垃圾的原因。
如果将displayInteger
的声明更改为
void displayInteger( char* str, const Integer &intObj )
即使没有显式的复制构造函数,你的代码也会“工作”。 但在任何情况下忽视“三个规则”都不是一个好主意。 以这种方式实施的类要么必须服从“三规则”,要么必须是不可复制的。
您遇到的问题是由默认的复制构造函数引起的,复制构造函数复制指针但不将其与新分配的内存相关联(就像复制构造函数的实现一样)。 按值传递对象时,会创建一个副本,当执行超出范围时,将复制此副本。 从析构函数中delete
会使intVal1
对象的value
指针无效,使其悬空指针 ,解除引用该指针会导致未定义的行为 。
调试输出可能用于理解代码的行为:
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
)上,后来被第三个副本使用,其中使用了这个悬空指针所指向的内存。
想想这个电话:
displayInteger( "intVal1", intVal1 );
您正在将intVal1
的副本创建到displayInteger
的intObj
参数中:
void displayInteger( char* str, Integer intObj )
{
cout << str << " is " << intObj.getInteger() << endl;
}
该副本将指向与intVal1
相同的int
。 当displayInteger
返回时, intObj
被销毁,这将导致int
被销毁,并且intVal1
的指针将指向无效对象。 此时,如果您尝试访问该值,则所有投注均已关闭(AKA未定义行为)。 类似的事情发生在intVal2
。
在更一般的层面上,通过删除复制构造函数,您违反了规则三,这通常会导致这些类型的问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.