2

I have a question regarding the assignment operator (apologies if this has already been answered in a different post).

As I understand the assignment operator, it is suppose to assign the value of one object to another, e.g.

class A {

 public:

  A();

  A& operator=(const A& rhs) 
  {         
    b = rhs.b;
    return *this;
  }

 private:

  B b;

};

Now, if the class contains pointers, it seems to be common practice to allocate new memory in the assignment operator. This makes the assignment operator more complicated since one needs to be more careful, e.g.

class A {

 public:

  A() : b(0) 
  {
    b = new B;
  }

  A& operator=(const A& rhs) 
  {         
    if (this != &rhs) {
      B* b1 = 0;

      try {
        b1 = new B(*rhs.b);
      }
      catch {
        delete b1;
        throw;
      }

      delete b;
      b = b1;
    }
    return *this;
  }

 private:

  B* b;

};

So my question is: Why not assign the value of the existing object pointed to, i.e. reuse the memory by calling B's assignment operator as one would do if b was not a pointer (see my first example)? Then the assignment would look like this

class A {

 public:

  A() : b(0) 
  {
    b = new B;
  }

  A& operator=(const A& rhs) 
  {         
    *b = *rhs.b;
    return *this;
  }

 private:

  B* b;

};

This is much more simple and does exactly what I would expect from the assignment operator.

Moreover, I can think of at least one example where the more complicated assignment operator will get me into trouble: If class A has a member function which returns b

const B* A::GetB() const 
{
  return b;
}

then the following code will leave a dangling pointer

A a1, a2;

const B* my_b = a1.GetB();

a1 = a2; // this leaves my_b dangling!

Your comments are much appreciated. Thanks!

  • 3
    Honestly, I'm a considerable fan of the [copy/swap idiom](http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom), and would advise it in this situation as well. Coding a proper copy-ctor and dtor, then exploiting *both* using a by-value assignment operator is a strong, viable alternative to how you're doing this now. Regarding the last problem, you'll have that no matter what you do. You cannot assume `b` is not reassigned a different allocation. If you *can*, then it has no business being dynamic in the first place and you should be using a const ref instead. – WhozCraig Apr 07 '14 at 20:23
  • Note your "sample" copy constructor leaks memory, and your original copy constructor is far more complex than necessary. http://coliru.stacked-crooked.com/a/197e34739dcff04e Also, the second is known as "copy on write" and is actually quite complex to implement all of the _other_ members (especially the destructor). Third, this question is very related to why move assignment was invented :D – Mooing Duck Apr 07 '14 at 20:32
  • @WhozCraig You're right that I cannot be sure that my_b will always point to the same memory (but this could be resolved by declaring it const B* const my_b = a1.GetB();). My concern was that my_b was invalidated by calling the assignment operator of A (which seems wrong to me). This would be avoided if using the by-value the assignment operator. – Kristian Gregersen Apr 07 '14 at 20:38
  • If using a const B* then you can only call const functions. – shawn1874 Apr 07 '14 at 20:49
  • @shawn1874 which is why GetB() is a const function. – Kristian Gregersen Apr 07 '14 at 20:59
  • @KristianGregersen: I must apologize, I totally overlooked the dereferences, that code is fine and does not leak. I deleted my incorrect comments. – Mooing Duck Apr 07 '14 at 21:41
  • @MooingDuck No worries, I'll delete my answers regarding this as well. Cheers – Kristian Gregersen Apr 07 '14 at 21:50
  • @KristianGregersen const or not makes no difference. `const B *p = a1.GetB();` followed by anything that releases or relocates the inner `a1.b1` (mandatory if it needs to expand) will dangle your pointer `p` into ether. Making `A::b1` const and initializing at construction won't help either. All it takes is a dynamic `A` or a simple scope-exit to squelch that. Sorry. but anyway you look at this, its a bad idea in the making. A shard pointer will solve the lifetime issue, but lose the tight coupling of having `p` reflect changes in a `A::b1` resize. You *can* use a reference, but tread lightly. – WhozCraig Apr 07 '14 at 22:27
  • @WhozCraig Well, I think my arguments are okay if _only_ considering what the assignment operator does. But, you're right that I can't be sure that `A::b`is not altered by other means such that `my_b` is invalidated. So, as you say, using `my_b` is a bad idea by construction. Thanks. – Kristian Gregersen Apr 07 '14 at 22:53
  • @KristianGregersen yeah, its just a lifetime thing. the scope issue would be likewise as-bad even if you used a reference. But at least with a reference *and* maintaining scope, if the underlying `A::b` changes, so shall said-change be reflected in the reference. Anyway, best of luck. – WhozCraig Apr 08 '14 at 00:34

3 Answers3

2

You should always reuse existing memory whenever possible. In you case, you should reuse the existing memory only if you are dead certain that b points to valid memory.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
1

It depends on the exact scenario at hand really. Usually, it is a good practice to reuse the memory, but really, when you are trying to assign a differently sized internal array, etc, you might be better off with reallocating or delete/new.

Note that, the own-cope check at the beginning would still be desired to avoid A = A alike situation, however.

László Papp
  • 51,870
  • 39
  • 111
  • 135
  • 1
    Are you sure the check for self-assignment is necessary when having a by-value assignment operator? I think this check is only needed in case you delete memory which you then immediately afterwards try to access, as is the case for the other version of the assignment operator. – Kristian Gregersen Apr 07 '14 at 20:55
  • If your intent is to not share pointers, then they shouldn't be the same. If they are, then who will eventually clean it up? Clearly there is no point in assigning when the two objects are the same. – shawn1874 Apr 07 '14 at 22:45
  • @shawn1874: sorry, I have no idea what you are trying to say. – László Papp Apr 07 '14 at 23:26
  • @KristianGregersen: the whole point is that it is not necessarily a "by-value assignment", so yes, I am sure it is needed when it is not. – László Papp Apr 07 '14 at 23:27
  • @LaszloPapp Are you referring to the situation where `B` itself contains pointers, in which case I would rely on the exact implementation of `B`'s assignment operator? – Kristian Gregersen Apr 07 '14 at 23:34
1

I can't think of any reason that you shouldn't try to do that. The examples that I have found where the memory is deleted is due to the fact that the type itself wasn't copyable. If you have a class attribute and it is a pointer to a copyable type then your suggestion should work conceptually (meaning that you only posted a partial snip so I am not saying that what is in your example is perfect). Some older examples show how it is done with char* and int* or other types of dynamic arrays. However, modern C++ programmers should be trying to use std::string and container classes. Now we also have smart pointers so that you can have shared pointers without having to worry about dangling references or worrying about which object is really the owner of it. Below is an example of what I mean. The example does use a template but copies as though the T is a c-array (not a container class) so deep copying has to be done as in your first example.
http://www.cplusplus.com/articles/y8hv0pDG/

shawn1874
  • 1,288
  • 1
  • 10
  • 26