1

Why do we need the delete statement?

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
}
celavek
  • 5,195
  • 5
  • 37
  • 68
cppcoder
  • 19,480
  • 6
  • 42
  • 77
  • 1
    The question itself and the `copy-constructor` tag don't fit the code - it's an copy assignment operator. – Frerich Raabe Jul 17 '11 at 08:16
  • Not directly relevant, but perhaps `delete[] this->str; this->length = rhs.length; this->str = new char[this->length]; strcpy(this->str, rhs.str);` would avoid an unnecessary call to `strlen` (assuming that `this->length == strlen(this->str)` is guaranteed by your implementation, of course.) – Chris Lutz Jul 17 '11 at 08:17
  • The simple answer is you don't. But I am sure the real answer is related to the rest of the design of the class that we are not seeing. But this is a very badly implemented assignment operator. – Martin York Jul 17 '11 at 08:52
  • This code is taken from a tutorial about Copy assignment operator. – cppcoder Jul 17 '11 at 16:58

5 Answers5

11

This is not a copy constructor, it's the assignment operator. You need the deleted because the object being assigned is holding already a previous value.

This code is also not really good because first deletes the old value and then allocates the new one... but the allocation may throw an exception and in this case the object will keep a pointer to a deallocated area. A better approach is first allocate and then delete the old value (an exception should never be allowed to escape from a destructor... see this link for an explanation) so either the assignment succeeds or fails without compromising anything.

A common idiom is to implement a copy constructor and a swap operation (that swaps the contents of two instances guaranteeing that no exception will be thrown). Then you implement the assignment operator combining the two... this requires both less code and is robust from an exception handling point of view.

Community
  • 1
  • 1
6502
  • 104,192
  • 14
  • 145
  • 251
  • "Cannot [throw an exception]" is a rather strong word. If you overloaded `operator delete` it _could_ throw an exception if you really wanted to. – Chris Lutz Jul 17 '11 at 08:19
  • Throwing an exception from a destructor is something to never do... the problem is that it's very common for destructors to be called during stack unwinding and if an exception escapes during that phase then very bad things happen. See http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.9 – 6502 Jul 17 '11 at 08:23
  • @6502: +1, `copy and swap` is the right way to do it, and You also might want to link to [this faq entry](http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom). – Alok Save Jul 17 '11 at 08:23
  • @6502 - I know you shouldn't, but it's not undefined (or even implementation-defined) behavior to throw an exception from a destructor. It's well defined - it works if it can, it calls `terminate` if it can't - and therefore saying "cannot" is too strong a word. If you said "should never," however, I would be totally okay with that. – Chris Lutz Jul 17 '11 at 08:25
  • Since the object is already created, why do we need to allocate memory again? Why not we set the value directly? – cppcoder Jul 17 '11 at 17:09
  • @cppcoder: that could be an option if the string length of the object being assigned is the same or even smaller than the current string lenth of this object. If the string length being assigned is however bigger then you need to allocate more memory. – 6502 Jul 17 '11 at 22:03
  • @cppcoder: You'd also like `VariableThatHeldBigString=MyString()` to release the old memory. – MSalters Jul 18 '11 at 08:54
5

The answer is that you have to release the memory because if you didn't then it would be lost, since you are reusing the pointer for a new allocation. Anyway, if you are learning about operators, it is common to write operator= in terms of copy construction + no-throw swap:

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;
   }
};

Note that the operations that are performed are the similar. Now the differences:

  • There is less overall code. Any operation that needs to be performed while copying will be implemented only in the copy constructor, the assignment operator borrows that code.

  • There is no need to check for self-assignment, as the copy is performed before the old memory is released

  • This implementation is exception safe: If there is a problem while allocating memory (or any other operation in the copy constructor) the operation has no side effects

  • Self assignment is less performant, as new memory is allocated, then copied and released. But assignment from temporaries (rvalue expressions) will be faster, as the compiler can elide the copy. Still performance should only be considered when and if measurements point at a bottleneck in the code.

David Rodríguez - dribeas
  • 192,922
  • 20
  • 275
  • 473
3

That's not a copy constructor. That's just a copy operator. So this->str will be pointing to previously allocated memory. If that memory is not freed before this->str is given a new value, then it will never be freed since its only reference has been overwritten. As a consequence, without the delete statement, that method would leak memory.

Keith Irwin
  • 5,514
  • 19
  • 28
0

Without the delete[] statement, you would be allocating memory without releasing it again. If your program is running sufficiently long, you would eventually run out of memory.

The new char[] call allocates new memory and yields a pointer to the allocated memory. You store this memory in this->str - overwriting the previous pointer stored in this->str.

Frerich Raabe
  • 81,733
  • 18
  • 105
  • 196
  • But the destructor (presumably) will do the delete. He is calling delete[] immediately followed by a new[]. Seems like redundant work to me. – Martin York Jul 17 '11 at 08:55
  • @Martin - Some constructor has (presumably) already allocated a buffer. That one has to be discarded if it isn't big enough (not checked here) so you can allocate a sufficiently large buffer. – Bo Persson Jul 17 '11 at 10:14
  • @Bo Persson: But it if it is big enough there would be no need to discard it. – Martin York Jul 17 '11 at 16:05
0

Refer to Scott Meyers, Effective C++, 2nd Ed., Items 11 through 17 for a thorough treatment of the subject.

And also: stackoverflow

Community
  • 1
  • 1
drb
  • 672
  • 8
  • 19