繁体   English   中英

赋值运算符与复制构造函数C ++

[英]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的副本创建到displayIntegerintObj参数中:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM