views:

308

answers:

4

Hi,

I just got a seg fault in overloading the assignment operator for a class FeatureRandomCounts, which has _rects as its pointer member pointing to an array of FeatureCount and size rhs._dim, and whose other date members are non-pointers:

FeatureRandomCounts &  FeatureRandomCounts::operator=(const FeatureRandomCounts &rhs)  
{  
  if (_rects) delete [] _rects;  

  *this = rhs;  // segment fault

  _rects = new FeatureCount [rhs._dim];  
  for (int i = 0; i < rhs._dim; i++)  
  {  
    _rects[i]=rhs._rects[i];  
  }  

  return *this;    
}

Does someone have some clue? Thanks and regards!

+14  A: 
*this = rhs;

calls operator=(), which is the function you are writing. Cue infinite recursion, stack overflow, crash.

Also, if you used a std::vector rather than a C-style array, you probably would not need to implement operator=() at all.

anon
Thanks, Neil! In my code, C array is prefered over std::vector. But it is still a possible solution.
Tim
If you prefer C arrays over vectors, you are wrong, and making a huge amount of unecessary work for yourself.
anon
The fact that you cant write the assignment operator correctly means that you probably don't know a whole host of other pitfalls associated with using dynamically allocated arrays. This means that you should definitely be using std::vector<>. Preferring a C array is not a reason but more a lack of understanding of what is really happening.
Martin York
Neil: Unless you have allocation requirements that can't be expressed through the std::allocator interface required by std::vector. Plus there are more resources which can be acquired and released than just memory, and pointers are often used as simply an example of that idiom (though I think you're right about this question).
Roger Pate
+3  A: 

The following line:

  *this = rhs;  // segment fault

will recursively call your operator=() function resulting in a stack overflow.

You should probably replace it with straight-forward assignments of the various member fields.

As Neil said, using something like std::vector<> will remove much of the responsibility away from your code. If for whatever reason you can't or don't want to use std::vector<>, you might also want to consider using the 'swap idiom' for your assignment operator. This will make the function exception safe (if the allocation of the memory for FeatureCount array fails and throws an exception, the original object that's being assigned to will be left unchanged). Something like the following:

void FeatureRandomCounts::swap( FeatureRandomCounts& other)
{
    FeatureCount* tmp_rects = other._rects;
    int tmp_dim             = other._dim;    // or whatever type _dim is

    // similarly for other members of FeatureRandomCounts...

    // now copy the other contents to 
    this->_rects = other._rects;
    this->_dim   = other._dim;

    // assign other members of rhs to lhs

    other._rects = tmp_rects;
    other._dim   = tmp_dim;

    // etc.

    return;
}

Now your assignment can look like:

FeatureRandomCounts &  FeatureRandomCounts::operator=(const FeatureRandomCounts &rhs)  
{  
    FeatureRandomCounts tmp( rhs);  // make a copy

    tmp.swap( *this);               // swap the contents of the copy and *this

    return *this;
                                    // the contents of tmp (which has the old 
                                    //  stuff that was in *this) gets destructed
}

Note that you need a proper copy constructor for this to work, but given the Big 3 rule you already need a proper copy ctor.

Michael Burr
In your implementation of swap() you could at least use std::swap() to swap the actual members around.
Martin York
@Martin - you're right, of course. I steered clear of `std::anything` because the OP indicated somewhere that he can't/won't use `std::vector`. I should have made that clear and will update the answer later to indicate that `std::swap` should be used (actually, it should be an non-scoped `swap()` with a proper `using` declaration so the most appropriate `swap()` gets picked up, with `std::swap` being the last resort match).
Michael Burr
+4  A: 
 *this = rhs;  // segment fault

This is definitively not the way to do it. You call = recursively, not calling the built in assignment operator. Assign variables one by one. Don't be lazy.

Kornel Kisielewicz
Thanks. I am lazy. So is it possible to call the default assignment operator?
Tim
No, once you declare your own, the compiler won't generate one (and couldn't, as it would have the same signature).
Roger Pate
+3  A: 

As mentioned, you have infinite recursion; however, to add to that, here's a foolproof way to implement op=:

struct T {
  T(T const& other);
  T& operator=(T copy) {
    swap(*this, copy);
    return *this;
  }
  friend void swap(T& a, T& b);
};

Write a correct copy ctor and swap, and exception safety and all edge cases are handled for you!

The copy parameter is passed by value and then changed. Any resources which the current instance must destroy are handled when copy is destroyed. This follows current recommendations and handles self-assignment cleanly.


#include <algorithm>
#include <iostream>

struct ConcreteExample {
  int* p;
  std::string s;

  ConcreteExample(int n, char const* s) : p(new int(n)), s(s) {}
  ConcreteExample(ConcreteExample const& other)
  : p(new int(*other.p)), s(other.s) {}
  ~ConcreteExample() { delete p; }

  ConcreteExample& operator=(ConcreteExample copy) {
    swap(*this, copy);
    return *this;
  }

  friend void swap(ConcreteExample& a, ConcreteExample& b) {
    using std::swap;
    //using boost::swap; // if available
    swap(a.p, b.p); // uses ADL (when p has a different type), the whole reason
    swap(a.s, b.s); // this 'method' is not really a member (so it can be used
                    // the same way)
  }
};

int main() {
  ConcreteExample a (3, "a"), b (5, "b");
  std::cout << a.s << *a.p << ' ' << b.s << *b.p << '\n';
  a = b;
  std::cout << a.s << *a.p << ' ' << b.s << *b.p << '\n';
  return 0;
}

Notice it works with either manually managed members (p) or RAII/SBRM-style members (s).

Roger Pate
Not quite get it. Is the structure a template? So by swap, copy will be the original *this?
Tim
Thanks! Does the swap change "copy" too? Is this more than assignment?
Tim