简体   繁体   English

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

[英]assignment operator vs. copy constructor C++

I have the following code to test out my understanding of basic pointers in 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;
}

This code works exactly as expected as is, it prints out: 此代码完全按预期工作,它打印出来:

intVal1 is 0

intVal2 is 10

intVal1 is 10

However if I remove the copy constructor it prints out something like: 但是,如果我删除了复制构造函数,它会输出如下内容:

intVal1 is 0

intVal2 is 10

intVal1 is 6705152

I don't understand why this is the case. 我不明白为什么会这样。 My understanding is that the copy constructor is used when the assignment is to an object that doesn't exist. 我的理解是,当赋值给不存在的对象时,使用复制构造函数。 Here intVal1 does exist, so why isn't the assignment operator called? 这里intVal1确实存在,那么为什么不调用赋值运算符?

The copy constructor is not used during assignment. 在分配期间不使用复制构造函数。 Copy constructor in your case is used when passing arguments to displayInteger function. 在将参数传递给displayInteger函数时,将使用您的案例中的复制构造函数。 The second parameter is passed by value, meaning that it is initailized by copy contructor. 第二个参数按值传递,这意味着它由复制构造函数初始化。

Your version of copy constructor performs deep copying of data owned by the class (just like your assignment operator does). 您的复制构造函数版本对类所拥有的数据执行深度复制(就像您的赋值运算符一样)。 So, everything works correctly with your version of copy constructor. 因此,一切都适用于您的版本的复制构造函数。

If you remove your own copy constructor, the compiler will generate one for you implicitly. 如果删除自己的复制构造函数,编译器将隐式生成一个。 The compiler-generated copy constructor will perform shallow copying of the object. 编译器生成的复制构造函数将执行对象的复制。 This will violate the "Rule of Three" and destroy the functionality of your class, which is exactly what you observe in your experiment. 这将违反“三法则”并破坏您班级的功能,这正是您在实验中观察到的。 Basically, the first call to displayInteger damages your intVal1 object and the second call to displayInteger damages your intVal2 object. 基本上,第一次调用displayInteger损坏你的intVal1对象,第二次调用displayInteger损坏你的intVal2对象。 After that both of your objects are broken, which is why the third displayInteger call displays garbage. 之后,两个对象都被破坏,这就是第三个displayInteger调用显示垃圾的原因。

If you change the declaration of displayInteger to 如果将displayInteger的声明更改为

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

your code will "work" even without an explicit copy constructor. 即使没有显式的复制构造函数,你的代码也会“工作”。 But it is not a good idea to ignore the "Rule of Three" in any case. 但在任何情况下忽视“三个规则”都不是一个好主意。 A class implemented in this way either has to obey the "Rule of Three" or has to be made non-copyable. 以这种方式实施的类要么必须服从“三规则”,要么必须是不可复制的。

The problem you're experiencing is caused by the default copy constructor, which copies the pointer but doesn't associate it with newly allocated memory (like your implementation of copy constructor does). 您遇到的问题是由默认的复制构造函数引起的,复制构造函数复制指针但不将其与新分配的内存相关联(就像复制构造函数的实现一样)。 When you pass object by value, a copy is created and when the execution goes out of scope, this copy is destructed. 按值传递对象时,会创建一个副本,当执行超出范围时,将复制此副本。 delete from the destructor invalidates the value pointer of intVal1 object, making it dangling pointer , dereferencing of which causes undefined behavior . 从析构函数中delete会使intVal1对象的value指针无效,使其悬空指针 ,解除引用该指针会导致未定义的行为

Debug outputs might be used to understand the behavior of your code: 调试输出可能用于理解代码的行为:

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;
}

Now output of this code: 现在输出这段代码:

Integer intVal1;
Integer intVal2(10);

foo( intVal1 );
foo( intVal2 );

intVal1 = intVal2;

foo( intVal1 );

is: 是:

ctor 构造函数
ctor(int) 构造函数(INT)
copy ctor 复制ctor
0x9ed4028 0 0x9ed4028 0
destructor 析构函数
copy ctor 复制ctor
0x9ed4038 10 0x9ed4038 10
destructor 析构函数
assignment 分配
copy ctor 复制ctor
0x9ed4048 10 0x9ed4048 10
destructor 析构函数
destructor 析构函数
destructor 析构函数

which shows that copy constructor is used when passing objects by value. 这表明在按值传递对象时使用了复制构造函数。 However, important to notice here is the destructor called upon the return from your function. 但是,重要的是要注意这里的析构函数是从函数返回的。 And if you remove your implementation of copy constructor, then the output is: 如果你删除你的拷贝构造函数的实现,那么输出是:

ctor 构造函数
ctor(int) 构造函数(INT)
0x8134008 0 0x8134008 0
destructor 析构函数
0x8134018 10 0x8134018 10
destructor 析构函数
assignment 分配
0x8134008 135479296 0x8134008 135479296
destructor 析构函数
destructor 析构函数
destructor 析构函数

showing that the first copy called delete on the same pointer (pointing to 0x8134008 ) as was used by third copy later, where the memory pointed by this dangling pointer has been used. 显示第一个副本名为delete在同一个指针(指向0x8134008 )上,后来被第三个副本使用,其中使用了这个悬空指针所指向的内存。

Think about this call: 想想这个电话:

displayInteger( "intVal1", intVal1 );

You are creating a copy of intVal1 into the intObj parameter of displayInteger : 您正在将intVal1的副本创建到displayIntegerintObj参数中:

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

That copy will be pointing to the same int that intVal1 is. 该副本将指向与intVal1相同的int When displayInteger returns, intObj is destroyed, which will cause the int to be destroyed, and the pointer in intVal1 to be pointing to an invalid object. displayInteger返回时, intObj被销毁,这将导致int被销毁,并且intVal1的指针将指向无效对象。 At that point all bets are off (AKA undefined behavior) if you try to access the value. 此时,如果您尝试访问该值,则所有投注均已关闭(AKA未定义行为)。 A similar thing happens for intVal2 . 类似的事情发生在intVal2

At a more general level, by removing the copy constructor, you are violating the Rule of Three, which typically leads to these kinds of problems. 在更一般的层面上,通过删除复制构造函数,您违反了规则三,这通常会导致这些类型的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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