使用复制构造函数和复制赋值运算符指向动态分配的内存

时间:2013-11-16 22:50:17

标签: c++ pointers memory

我正在查看三规则的解释,并找到以下代码:

// 1. copy constructor
person(const person& that)
{
    name = new char[strlen(that.name) + 1];
    strcpy(name, that.name);
    age = that.age;
}

// 2. copy assignment operator
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;
}

解释事物(见What is The Rule of Three?) 我不确定我是否应该在那里提出这个问题(因为它已经发布了一段时间)或提出一个新问题,所以我希望我没有遇到麻烦。我只是想看看我是否正确理解了这段代码?

从我的理解是,不是复制指针(因此有两个对象指向同一个内存),被复制的对象是动态分配新内存,然后包含与对象相同的数据这是复制包含?那么复制赋值运算符函数在开头有“delete [] name]”的原因是什么?是因为当使用=时,指针会自动复制,因此这个新对象指向与复制对象相同的内存,而删除[]名称会在动态分配新内存之前删除该内存以使其指向?实际上,我不确定我在说什么,所以有人可以向我解释为什么它使用删除?最后,if(this!=& that)部分的目的是什么?

谢谢!

4 个答案:

答案 0 :(得分:3)

delete的要点是释放this字段name对象先前拥有的内存。请注意,这是赋值运算符重载,即:

A x,y;
x = y;

会使用this == &xthat == &y调用该函数。

这会将y的内部成员复制到x,同时确保释放先前分配给x.name的任何动态分配的内存

答案 1 :(得分:1)

成员变量name是一个指针。它(几乎总是)指向在堆上分配的char[](即new)。

我们想将另一个对象的名称复制到这个名称:

strcpy(name, that.name);

但是之前我们必须确保name指向的数组足以容纳新名称(以及终止'\ 0'),因此我们分配空间与new

name = new char[strlen(that.name) + 1];

但是 name之前指向的空间怎么样?我们不想放弃它,那将是内存泄漏,所以在重新分配指针之前我们应该delete

delete[] name;

对于this != &that,考虑如果一些粗心的人使用从一个person到其自身(Alice = Alice)的赋值运算符会发生什么。完成这些步骤,您将看到该名称将完全丢失。

答案 2 :(得分:1)

首先,此赋值运算符是错误示例!特别是它不是线程安全的!如果你想编写一个合适的赋值运算符,你可以这样写:

person& operator=(person other) {
    other.swap(*this);
    return this;
}

其中swap()是您想要的成员函数,只需交换所有成员:

void swap(person& other) {
    std::swap(this->name, other.name);
    std::swap(this->age,  other.age);
}

关于你的问题:

  1. 作者有助于检查if (this != &that)以表明代码错误!基本上有两种情况是错误的:
    1. 这种比较的想法是防止自我分配。如果此检查后的代码实际上取决于此检查是否正确,则几乎可以肯定不是例外安全(很少,之后的代码提供了基本保证;我从未见过它最终实现强异常安全的示例保证虽然没有理由让转让操作员不实施强烈的异常安全保证。)
    2. 如果代码就在那里而不是严格需要,那就是“优化”自我赋值案例。 ......并且影响所有分配的自我分配的情况,希望是绝大多数!如果您的代码主要是忙于为自己分配对象,那么代码中就会出现更为根本的问题。
  2. C ++没有任何垃圾收集:如果你分配了内存,你需要释放内存(当然,对于所有其他资源都是如此)。使用new在C ++中分配内存有两种形式:
    1. 您可以使用new T(args)new T{args}分配单个对象,其中T不是typedef数组类型,args是占位符构造函数参数。这样分配的内存需要使用delete ptr释放,其中ptr是从上面的表达式返回的结果。通常,内存不是显式释放,而是传递给负责释放内存的对象,例如std::unique_ptr<T>(new T(args))std::unique_ptr<T>的析构函数将根据需要调用delete
    2. 您可以分配数组对象,通常使用new T[n],其中n是数组的大小。这样分配的对象需要使用delete[] array释放,其中array是从数组分配返回的结果。这很少见,因为通常您宁愿使用std::stringstd::vector<T>来分配数组。您还可以使用上面提到的类模板,但需要指明需要发布数组:std::unique_ptr<T[]>(new T[n])

答案 3 :(得分:0)

delete[] name;的原因是类不变量是name指向动态分配的缓冲区,其大小完全符合人名。请注意,我们即将创建一个新的缓冲区来容纳that.name中存储的数据副本。如果我们没有delete[]我们原来的name指向的内容,我们就会泄漏那个记忆。

对于if (this != &that),只需在心理上跟踪如果不存在会发生什么,并且有人在x = x对象上调用person x。首先,我们delete[] name。从this == &that开始,这也意味着this->name == that.name,因此我们也使that.name指向的缓冲区无效。在下一步中,我们在(现在无效的)缓冲区上调用strlen(),该缓冲区提供未定义的行为(可能是例如段错误)。