【问题标题】:copy assignment operator clarification复制赋值运算符说明
【发布时间】:2020-04-09 08:45:20
【问题描述】:

我正在为我创建的一个类编写一个复制赋值运算符,并使用以前的帖子作为指导: What is The Rule of Three?

我对这个人的解释的一个方面有点困惑。

这是他们的班级:

class person
{
    char* name;
    int age;
};

这是我用作参考的复制赋值运算符定义(至少提供了 2 个):

// 2. copy assignment operator
person& operator=(const person& that)
{
    char* local_name = new char[strlen(that.name) + 1];
    // If the above statement throws,
    // the object is still in the same state as before.
    // None of the following statements will throw an exception :)
    strcpy(local_name, that.name);
    delete[] name;
    name = local_name;
    age = that.age;
    return *this;
}

我感到困惑的是,为什么它们包括delete[] name; 行?

这是他们提供的另一个例子:

person& operator=(const person& that)
{
    if (this != &that)
    {
        delete[] name;
        // This is a dangerous point in the flow of execution!
        // We have temporarily invalidated the class invariants,
        // and the next statement might throw an exception,
        // leaving the object in an invalid state :(
        name = new char[strlen(that.name) + 1];
        strcpy(name, that.name);
        age = that.age;
    }
    return *this;
}

我立即回避了这个,因为我不明白为什么该函数会检查 if(this != &that) 然后在似乎尚未生成的数组上运行 delete (delete[] name;)。调用赋值运算符时,是否在调用复制赋值运算符函数之前立即调用常规构造函数?因此意味着我们必须删除类构造函数生成的数组,因为它只能充满垃圾数据?

为什么我不能写: name = that.name

name = new char[that.name.size()];
for (int i = 0; i < that.name.size(); i++)`
{
  name[i] = that.name[i]
}

这可能是非常基本的,我应该只实现帖子的建议,但我的实际用例涉及一个包含多个成员的 structs 数组,所以我只是有点难以理解我到底需要什么去做。

我知道这里有大约 2.5 个问题。任何见解将不胜感激。

这是我第一次实现自定义复制构造函数和复制赋值运算符,我相信在我完成几次之后会看起来很容易。

提前致谢。

【问题讨论】:

  • 如果新旧name 的长度相同,您可以重用name 指针。但如果不是,它必须指向一个新的地方。
  • person temp(that); std::swap(temp.name, name); std::swap(temp.age, age); return *this; 使用复制/交换而不是您的问题,这将是整个赋值运算符,前提是存在复制构造函数和析构函数。
  • 更多关于Copy and Swap
  • 可以在structs的数组上使用copy()和swap()吗?
  • operator= 在已存在的对象上运行。该对象必须是在代码中较早的某个时间点构建的,以便您可以对其进行赋值。例如。 Person p; /* ... */ p = q;

标签: c++ class operator-overloading copy-assignment


【解决方案1】:

我感到困惑的是,为什么它们包括行 delete[] name;?

当使用newnew[] 管理内存时,经验法则是它们应该有一个匹配的deletedelete[]。据推测,name 之前是用new[] 分配的(在构造函数中或通过先前的赋值),所以在用name = local_name; 在下一行重新分配它之前,我们需要delete[] 它。

我不明白为什么函数会检查 if(this != &that)

此检查是一个健全性检查。如果您将对象分配给自身,则无需执行任何操作。实际上,它可能会导致问题,因为如果您在this 指向与that 引用相同的对象时delete[] name;,那么您将无法再复制that.name,因为它的内存已被释放。

为什么我不能只写:name = that.name 或

这将使name 在两个不同的实例中指向同一个内存。在某些情况下,这样的共享内存不仅有用而且是理想的。但是,您必须小心,因为如果其中一个实例对其名称执行 delete[],那么另一个实例中的 name 将不再有效。

【讨论】:

  • name 之前在该对象的构造函数中分配有new[]?这就是让我感到困惑的地方,为什么不是delete[] that.name 行?我可以理解我们是否正在删除旧对象的内存资源,但为什么我们要删除新对象的内存资源,我认为这些资源甚至还没有分配?除非,对象的构造函数在复制赋值运算符代码运行之前运行。
  • @MagicGAT "之前在该对象的构造函数中使用 new[] 分配了名称?"是的,无论是在构造函数中还是在之前对operator=() 的调用中。
  • @MagicGAT "这让我很困惑,为什么 delete[] that.name 行不是?新对象的内存资源”不要将这些视为“旧”与“新”对象。它们是赋值运算符的左侧和右侧。左边的对象大概已经有一个分配内存的name 字段。但我们正在覆盖该内存,在此之前,我们需要释放name 指向的内存以避免内存泄漏。
  • 好的,这更有意义。因为如果在一个对象上使用=,它必须已经实例化(即使 C++ 代码实例化了该对象,然后在同一行中为其分配了另一个对象的值)
  • @MagicGAT 假设你有分配a = b。那么如果operator=() 执行delete[] that.name;,那么b.name 不再指向有效的内存地址。这将导致令人惊讶的行为,因为我们不希望分配修改 b
【解决方案2】:

所有这些都是确保正确管理内存所必需的。

如果我理解正确,您需要一个operator= 实际调用时间的答案?
嗯,operator= 总是被调用当两个有效对象存在时。其中一个是分配给的,第二个是数据源。在此操作之后,两个对象必须仍然有效

这意味着在operator= 内部,this 对象的内存分配给了一个字符串(在其中一个 contrusctors 中分配)和that 对象,内存也分配给了另一个字符串。

我感到困惑的是,为什么它们包含delete[] name; 行?

我们必须首先清理当前驻留在this 对象中的内存,否则我们会在分配新的妈妈后丢失这个指针。

调用赋值运算符时,是否在调用复制赋值运算符函数之前立即调用常规构造函数?

没有。该对象已经存在,这就是调用 operator= 的原因(而不是复制构造函数)。

因此意味着我们必须删除类构造函数生成的数组,因为它只能充满垃圾数据?

它充满了有效数据。您的对象已构建,其中包含一些数据,然后您将其他内容分配给该对象。


附录:何时调用复制构造函数,何时调用operator=(有关更多详细信息,请参阅this question):

class A{};

int main()
{
    A a1; //default constructor 
    A a2 = a1; //copy-constructor, not assignment operator! New object is needed

    A a3; 
    a3 = a1; //we have valid and existing object on the left side, assignment operator is used
}

【讨论】:

  • 非常感谢,在看到这个之前我已经接受了第一个答案。两者都相当不错。谢谢你花时间写这篇文章。我也会查看链接的帖子。
【解决方案3】:

让我们从关于这段代码sn-p的问题开始。

name = new char[that.name.size()];
for (int i = 0; i < that.name.size(); i++)`
{
  name[i] = that.name[i]
}

数据成员名称的类型为 char *。那就是它是一个指针。指针和指针指向的字符都没有像size 这样的成员函数。

所以这段代码sn-p是无效的,不会编译。

你可以使用这样的方法

name = that.name;

如果数据成员的类型为std::string

为什么我不能直接写:name = that.name

在这种情况下,该类的两个对象将包含指向相同内存范围的指针。因此,如果一个对象将被删除,那么另一个对象的指针将是无效的,并且使用它来删除分配的内存会导致未定义的行为。

当调用赋值运算符时,是正则构造函数 在复制赋值运算符函数之前立即调用 叫什么?

复制赋值运算符只能应用于已经构造的对象。也就是说,该类的对象必须已经存在,并且使用的构造函数应通过nullptr 或分配的内存地址初始化其数据成员名称。

我立即回避了这个,因为我无法理解 为什么函数会检查 if(this != &that)

此检查可以避免对象的自分配。也就是说,当一个对象被分配给自己时,分配和删除内存的代码将是多余的。

我感到困惑的是,为什么它们包括该行 删除[] 名称;?

因为对象已经被构造,并且它的数据成员名称可以指向包含名称字符串的分配内存。

考虑到所有内容,复制赋值运算符可能看起来像

person& operator =( const person &that )
{
    if ( this != &that )
    {
        char *local_name = new char[strlen(that.name) + 1];
        // If the above statement throws,
        // the object is still in the same state as before.
        // None of the following statements will throw an exception :)
        strcpy(local_name, that.name);
        delete[] name;
        name = local_name;
        age = that.age;
    }

    return *this;
}

【讨论】:

  • 好的,我现在看到复制赋值运算符与实例化无关。感谢您的澄清。
猜你喜欢
  • 2013-06-15
  • 1970-01-01
  • 2017-11-21
  • 2023-04-06
  • 2011-11-16
  • 2013-12-04
  • 2020-02-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多