20

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

4

3 回答 3

20

在赋值期间不使用复制构造函数。在您将参数传递给函数时使用复制构造displayInteger函数。第二个参数是按值传递的,也就是说它是由复制构造函数初始化的。

您的复制构造函数版本对类拥有的数据执行深度复制(就像您的赋值运算符一样)。因此,一切都适用于您的复制构造函数版本。

如果您删除自己的复制构造函数,编译器会为您隐式生成一个。编译器生成的复制构造函数将执行对象的拷贝。这将违反“三法则”并破坏您的课程的功能,这正是您在实验中观察到的。基本上,第一次调用会displayInteger损坏您的intVal1对象,第二次调用会displayInteger损坏您的intVal2对象。之后你的两个对象都被破坏了,这就是第三次displayInteger调用显示垃圾的原因。

如果您将声明更改displayInteger

void displayInteger( char* str, const Integer &intObj )

即使没有显式的复制构造函数,您的代码也将“工作”。但无论如何,忽视“三法则”并不是一个好主意。以这种方式实现的类要么必须遵守“三法则”,要么必须使其不可复制。

于 2013-09-23T21:26:44.637 回答
4

您遇到的问题是由默认的复制构造函数引起的,它复制指针但不将其与新分配的内存相关联(就像您的复制构造函数的实现一样)。当您按值传递对象时,将创建一个副本,并且当执行超出范围时,该副本将被破坏。deletefrom 析构函数使对象的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 );

是:

ctor
ctor(int)
copy ctor
0x9ed4028 0
destructor
copy ctor
0x9ed4038 10
destructor
assignment
copy ctor
0x9ed4048 10
destructor
destructor
destructor

这表明在按值传递对象时使用了复制构造函数。但是,这里需要注意的重要一点是函数返回时调用的析构函数。如果您删除复制构造函数的实现,则输出为:

ctor
ctor(int)
0x8134008 0
析构函数
0x8134018 10
析构函数
赋值
0x8134008 135479296
析构函数析构函数析

函数

显示第一个副本调用delete的指针(指向0x8134008)与后来的第三个副本使用的指针相同,其中该悬空指针指向的内存已被使用。

于 2013-09-23T21:49:31.877 回答
3

想想这个电话:

displayInteger( "intVal1", intVal1 );

intVal1您正在为 的intObj参数创建副本displayInteger

void displayInteger( char* str, Integer intObj )
{
  cout << str << " is " << intObj.getInteger() << endl;
}

该副本将指向相同intintVal1内容。displayInteger返回时,被intObj销毁,这将导致int被销毁,并且指针 inintVal1指向一个无效的对象。到那时,如果您尝试访问该值,所有赌注都将关闭(AKA 未定义行为)。类似的事情发生在intVal2.

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

于 2013-09-23T21:33:51.427 回答