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

时间:2013-09-23 21:19:11

标签: c++ pointers memory-management copy-constructor

我有以下代码来测试我对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确实存在,为什么不调用赋值运算符?

3 个答案:

答案 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

在更一般的层面上,通过删除复制构造函数,您违反了规则三,这通常会导致这些问题。