【问题标题】:Why do we need to delete allocated memory in C++ assignment operator? [duplicate]为什么我们需要在 C++ 赋值运算符中删除分配的内存? [复制]
【发布时间】:2011-07-17 08:11:58
【问题描述】:

为什么我们需要删除语句?

const MyString& operator=(const MyString& rhs)
{ 
    if (this != &rhs) {
        delete[] this->str; // Why is this required?
        this->str = new char[strlen(rhs.str) + 1]; // allocate new memory
        strcpy(this->str, rhs.str); // copy characters
        this->length = rhs.length; // copy length
    }
    return *this; // return self-reference so cascaded assignment works
}

【问题讨论】:

  • 问题本身和copy-constructor 标签不适合代码 - 它是一个复制赋值运算符。
  • 不直接相关,但delete[] this->str; this->length = rhs.length; this->str = new char[this->length]; strcpy(this->str, rhs.str); 可能会避免对strlen 的不必要调用(当然,假设this->length == strlen(this->str) 由您的实现保证。)
  • 简单的答案是你不知道。但我确信真正的答案与我们没有看到的类的其余设计有关。但这是一个非常糟糕的赋值运算符。
  • 这段代码取自关于复制赋值运算符的教程。

标签: c++


【解决方案1】:

这不是复制构造函数,它是赋值运算符。您需要删除,因为被分配的对象已经保存了以前的值。

这段代码也不是很好,因为首先删除旧值然后分配新值...但是分配可能会引发异常,在这种情况下,对象将保留指向已释放区域的指针。更好的方法是先分配然后删除旧值(绝不应允许异常从析构函数中逃逸...see this link for an explanation),这样分配成功或失败都不会影响任何事情。

一个常见的习惯用法是实现一个复制构造函数和一个交换操作(交换两个实例的内容,保证不会抛出异常)。然后你实现assignment operator combining the two...这需要更少的代码并且从异常处理的角度来看是健壮的。

【讨论】:

  • “不能[抛出异常]”是一个相当强的词。如果你重载了operator delete,它可能抛出异常,如果你真的想的话。
  • 从析构函数中抛出异常是永远不会做的事情......问题是在堆栈展开期间调用析构函数是很常见的,如果在该阶段发生异常逃逸,那么会发生非常糟糕的事情.见parashift.com/c++-faq-lite/exceptions.html#faq-17.9
  • @6502: +1, copy and swap 是正确的做法,你也可以链接到this faq entry
  • @6502 - 我知道你不应该这样做,但从析构函数中抛出异常并不是未定义(甚至是实现定义)的行为。它定义明确 - 如果可以,它会工作,如果不能,它会调用terminate - 因此说“不能”这个词太强了。但是,如果您说“不应该”,我完全可以接受。
  • 既然对象已经创建好了,为什么还要重新分配内存呢?为什么不直接设置值呢?
【解决方案2】:

答案是你必须释放内存,因为如果你不这样做,它就会丢失,因为你正在重用指针进行新的分配。无论如何,如果你正在学习运算符,通常写operator= 以复制构造+不抛出交换:

class MyString {
   char* str;
   int len;
public:
   MyString( const MyString& rhs ) : str( new char[ rhs.len ] ), len( rhs.len ) {
      memcpy( str, rhs.str, len );
   }
   ~MyString() {
      delete str;
   }
   friend void swap( MyString & lhs, MyString & rhs ) throw() {
      using std::swap;
      swap( lhs.str, rhs.str );
      swap( lhs.len, rhs.len );
   }
   MyString& operator=( MyString rhs ) { //note: by value
      swap( *this, rhs );
      return *this;
   }
};

请注意,执行的操作是相似的。现在的区别:

  • 整体代码较少。复制时需要执行的任何操作都将仅在复制构造函数中实现,赋值运算符借用该代码。

  • 无需检查自分配,因为复制是在旧内存被释放之前执行的

  • 此实现是异常安全的:如果在分配内存(或复制构造函数中的任何其他操作)时出现问题,则该操作没有副作用

  • 自分配性能较低,因为分配了新内存,然后复制并释放。但是临时变量(右值表达式)的赋值会更快,因为编译器可以省略副本。只有当测量值指向代码中的瓶颈时,才应考虑静态性能。

【讨论】:

    【解决方案3】:

    这不是复制构造函数。那只是一个复制运算符。所以 this->str 将指向先前分配的内存。如果在 this->str 被赋予新值之前没有释放该内存,那么它将永远不会被释放,因为它的唯一引用已被覆盖。因此,如果没有 delete 语句,该方法会泄漏内存。

    【讨论】:

    • 你需要给它一个新的值吗?为什么不 jsut 重用那里的价值?
    • 为什么要再次分配内存?我们可以将 vale 设置为已经分配的内存对吗?
    • @cppcoder:除非大小发生变化。考虑到您的字符串可能包含“Hi”,并且您可能希望将其更新为“早安朋友!”这可能会占用比您当前分配的内存更多的内存。
    【解决方案4】:

    如果没有delete[] 语句,您将分配内存而不会再次释放它。如果您的程序运行时间足够长,您最终会耗尽内存。

    new char[] 调用分配新内存并生成指向已分配内存的指针。您将此内存存储在 this->str 中 - 覆盖之前存储在 this->str 中的指针。

    【讨论】:

    • 但是析构函数(大概)会执行删除。他立即调用 delete[],然后是 new[]。对我来说似乎是多余的工作。
    • @Martin - 一些构造函数(可能)已经分配了一个缓冲区。如果它不够大(此处未检查),则必须丢弃该缓冲区,以便您可以分配足够大的缓冲区。
    • @Bo Persson:但如果它足够大,就不需要丢弃它。
    【解决方案5】:

    请参阅 Scott Meyers,Effective C++,第 2 版,第 11 至 17 条,以全面了解该主题。

    还有:stackoverflow

    【讨论】:

      猜你喜欢
      • 2015-05-01
      • 2021-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-27
      • 2018-09-14
      • 1970-01-01
      • 2012-04-27
      相关资源
      最近更新 更多