【问题标题】:Unclear Copy assignment operator example不明确的复制赋值运算符示例
【发布时间】:2023-04-06 17:58:01
【问题描述】:

我一直在 this page 上查找一些 C++ 内容。

下面是关于复制赋值运算符的示例:

Example5& operator= (const Example5& x) {
  delete ptr;                      // delete currently pointed string
  ptr = new string (x.content());  // allocate space for new string, and copy
  return *this;
}   

到目前为止,这对我来说很清楚,但是文章指出:

甚至更好,因为它的字符串成员不是常量,它可以重新使用相同的字符串对象:

Example5& operator= (const Example5& x) {
  *ptr = x.content();
  return *this;
}

我不明白为什么需要这样做。第一个示例不正是我们想要实现的吗?:复制 分配内容。为什么要“重用同一个字符串对象”?

【问题讨论】:

  • 重复使用同一个对象可以消除不必要的内存释放和分配。
  • 删除对象并创建新对象是不必要的开销,因为 ptr 不是 const 您可以修改其内容。你还在抄袭。
  • using namespace std; [...] string* ptr;。这足以让我把这个“教程”扔掉。
  • @uhohsomebodyneedsapupper 这个内存泄漏情况如何?
  • @uhohsomebodyneedsapupper 不,你错了。这段代码很好。您只需将 assign content() 复制到您已经创建的字符串。只要 ptr 在你的 dtor 中被清理并且 std::string 的实现是正确的(你猜怎么着,除非你使用一个外来的编译器,这比真的更真实)这里就不会有任何内存泄漏。跨度>

标签: c++ c++11


【解决方案1】:

该页面指出:

[...]隐式版本执行浅拷贝,适用于 许多类,但不适用于具有指向它们处理的对象的指针的类 它的存储,如示例 5 中的情况。在这种情况下,不仅 类会产生两次删除指向对象的风险,但是 分配通过不删除指向的对象来创建内存泄漏 赋值前的对象。

让我们再看一遍代码:

#include <iostream>
#include <string>
using namespace std;

class Example5 {
    string* ptr;
  public:
    Example5 (const string& str) : ptr(new string(str)) {}
    ~Example5 () {delete ptr;}
    // copy constructor:
    Example5 (const Example5& x) : ptr(new string(x.content())) {}
    // access content:
    const string& content() const {return *ptr;}
};

所以复制赋值运算符的隐式版本将等价于

Example5& operator= (const Example5& x) {
  ptr = x.ptr; // previous value of ptr is lost -> memory leak
  return *this;
}

这会造成内存泄漏,因为不会通过this.ptr 释放元素指针。 建议的版本取消分配ptr然后为其分配新内存。重新分配和分配会产生额外的成本和额外的指令,因此第二个代码

Example5& operator= (const Example5& x) {
  *ptr = x.content();
  return *this;
}

重复使用 (this->ptr) 的相同(已分配)内存区域来存储 *x.ptr 的副本。

请注意,由于x.content() 返回了x 中字符串的副本,*ptr = x.content(); 将调用*ptr 的copy assignment operator,在这种情况下是字符串。

【讨论】:

  • 好的,但据我所知,只有在示例 5 的构造过程中肯定创建/分配了 string* ptr; 时,这才可以接受?我想我被困住了,因为*ptr = x.content(); 看起来像是一种冒险的任务,假设ptr已经存在。
  • 看起来有风险,但实际上x.content() 会返回*x.ptr 的副本,并调用字符串的复制赋值运算符。该示例强制在调用构造函数时分配字符串,所以是的,它在这些情况下工作。
【解决方案2】:

与 Java 和其他一些语言不同,*prt = x.content() 将从x 复制一个字符串。 主要好处是您不需要删除和重新分配 ptr 指向的字符串对象的内存(这是非常昂贵的操作),而是使用 std:string 的类复制赋值运算符。字符串复制赋值运算符(用于左值引用)也具有很强的异常保证 - 因此,如果该方法内部发生某些事情 - 所有对象的状态都是有效的。并且不要忘记很有可能(如果 ptr->length()

【讨论】:

  • 该类首先应该有一个字符串成员,而不是指向字符串的指针。
  • @DanielDaranas 是为了什么?
  • 为简单起见。无论如何,他都在使用复制语义。他基本上是在对象中存储(并拥有)一个字符串。
  • @DanielDaranas 是的,那会更容易,但这是另一个问题(我认为是设计问题)。
猜你喜欢
  • 2020-04-09
  • 2017-11-21
  • 1970-01-01
  • 1970-01-01
  • 2019-04-12
  • 2011-11-16
  • 2013-12-04
  • 2020-02-28
  • 1970-01-01
相关资源
最近更新 更多