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

时间:2022-01-27 02:58:32

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确实存在,那么为什么不调用赋值运算符?

3 个解决方案

#1


19  

The copy constructor is not used during assignment. Copy constructor in your case is used when passing arguments to displayInteger function. The second parameter is passed by value, meaning that it is initailized by copy contructor.

在分配期间不使用复制构造函数。在将参数传递给displayInteger函数时,将使用您的案例中的复制构造函数。第二个参数按值传递,这意味着它由复制构造函数初始化。

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. After that both of your objects are broken, which is why the third displayInteger call displays garbage.

如果删除自己的复制构造函数,编译器将隐式生成一个。编译器生成的复制构造函数将执行对象的浅复制。这将违反“三个规则”并破坏您班级的功能,这正是您在实验中观察到的。基本上,第一次调用displayInteger会损坏你的intVal1对象,第二次调用displayInteger会损坏你的intVal2对象。之后,两个对象都被破坏,这就是第三个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.

即使没有明确的复制构造函数,您的代码也将“正常工作”。但在任何情况下忽视“三个规则”都不是一个好主意。以这种方式实施的类要么必须遵守“三规则”,要么必须是不可复制的。

#2


4  

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.

您遇到的问题是由默认的复制构造函数引起的,该复制构造函数复制指针但不将其与新分配的内存相关联(就像您的复制构造函数的实现一样)。按值传递对象时,会创建一个副本,当执行超出范围时,将复制此副本。从析构函数中删除会使intVal1对象的值指针无效,使其悬空指针,解除引用该指针会导致未定义的行为。

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)
copy ctor
0x9ed4028 0
destructor
copy ctor
0x9ed4038 10
destructor
assignment
copy ctor
0x9ed4048 10
destructor
destructor
destructor

ctor ctor(int)copy ctor 0x9ed4028 0析构函数复制ctor 0x9ed4038 10析构函数赋值复制ctor 0x9ed4048 10析构函数析构函数析构函数

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)
0x8134008 0
destructor
0x8134018 10
destructor
assignment
0x8134008 135479296
destructor
destructor
destructor

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

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)上,后来被第三个副本使用,其中使用了这个悬空指针所指向的内存。

#3


2  

Think about this call:

想想这个电话:

displayInteger( "intVal1", intVal1 );

You are creating a copy of intVal1 into the intObj parameter of displayInteger:

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

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

That copy will be pointing to the same int that intVal1 is. 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. At that point all bets are off (A.K.A. undefined behavior) if you try to access the value. A similar thing happens for intVal2.

该副本将指向与intVal1相同的int。当displayInteger返回时,intObj被销毁,这将导致int被销毁,并且intVal1中的指针将指向无效对象。此时,如果您尝试访问该值,则所有投注均已关闭(A.K.A.未定义的行为)。对于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.

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

#1


19  

The copy constructor is not used during assignment. Copy constructor in your case is used when passing arguments to displayInteger function. The second parameter is passed by value, meaning that it is initailized by copy contructor.

在分配期间不使用复制构造函数。在将参数传递给displayInteger函数时,将使用您的案例中的复制构造函数。第二个参数按值传递,这意味着它由复制构造函数初始化。

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. After that both of your objects are broken, which is why the third displayInteger call displays garbage.

如果删除自己的复制构造函数,编译器将隐式生成一个。编译器生成的复制构造函数将执行对象的浅复制。这将违反“三个规则”并破坏您班级的功能,这正是您在实验中观察到的。基本上,第一次调用displayInteger会损坏你的intVal1对象,第二次调用displayInteger会损坏你的intVal2对象。之后,两个对象都被破坏,这就是第三个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.

即使没有明确的复制构造函数,您的代码也将“正常工作”。但在任何情况下忽视“三个规则”都不是一个好主意。以这种方式实施的类要么必须遵守“三规则”,要么必须是不可复制的。

#2


4  

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.

您遇到的问题是由默认的复制构造函数引起的,该复制构造函数复制指针但不将其与新分配的内存相关联(就像您的复制构造函数的实现一样)。按值传递对象时,会创建一个副本,当执行超出范围时,将复制此副本。从析构函数中删除会使intVal1对象的值指针无效,使其悬空指针,解除引用该指针会导致未定义的行为。

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)
copy ctor
0x9ed4028 0
destructor
copy ctor
0x9ed4038 10
destructor
assignment
copy ctor
0x9ed4048 10
destructor
destructor
destructor

ctor ctor(int)copy ctor 0x9ed4028 0析构函数复制ctor 0x9ed4038 10析构函数赋值复制ctor 0x9ed4048 10析构函数析构函数析构函数

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)
0x8134008 0
destructor
0x8134018 10
destructor
assignment
0x8134008 135479296
destructor
destructor
destructor

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

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)上,后来被第三个副本使用,其中使用了这个悬空指针所指向的内存。

#3


2  

Think about this call:

想想这个电话:

displayInteger( "intVal1", intVal1 );

You are creating a copy of intVal1 into the intObj parameter of displayInteger:

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

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

That copy will be pointing to the same int that intVal1 is. 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. At that point all bets are off (A.K.A. undefined behavior) if you try to access the value. A similar thing happens for intVal2.

该副本将指向与intVal1相同的int。当displayInteger返回时,intObj被销毁,这将导致int被销毁,并且intVal1中的指针将指向无效对象。此时,如果您尝试访问该值,则所有投注均已关闭(A.K.A.未定义的行为)。对于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.

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