views:

602

answers:

1

The C++ move constructor and move assignment operator seem like really positive things. And they can be used in situations where the copy constructor makes no sense because they don't require duplicating resources being pointed at.

But there are cases where they will bite you if you aren't careful. And this is especially relevant as I've seen proposals to allow the compiler to generate default implementations of the move constructor. I will provide a link to such if someone can give me one.

So, here is some code that has some flaws that may not be completely obvious. I tested the code to make sure it compiles in g++ with the -std=gnuc++0x flag. What are those flaws and how would you fix them?

#if (__cplusplus <= 199711L) && !defined(__GXX_EXPERIMENTAL_CXX0X__)
   #error This requires c++0x
#endif

#include <unordered_set>
#include <vector>
#include <utility>
#include <algorithm>

class ObserverInterface {
 public:
   virtual ~ObserverInterface() {}

   virtual void observedChanged() = 0;
   virtual void observedGoingAway() = 0;
};

class Observed {
 private:
   typedef ::std::unordered_set<ObserverInterface *> obcontainer_t;

 public:
   Observed() {}
   Observed(const Observed &) = delete;
   const Observed &operator =(const Observed &b) = delete;
   // g++ does not currently support defaulting the move constructor.
   Observed(Observed &&b) : observers_(::std::move(b.observers_)) { }
   // g++ does not currently support defaulting move assignment.
   const Observed &operator =(Observed &&b) {
      observers_ = ::std::move(b.observers_);
      return *this;
   }
   virtual ~Observed() {
      for (auto i(observers_.begin()); i != observers_.end(); ++i) {
         (*i)->observedGoingAway();
      }
   }

   void unObserve(ObserverInterface *v) {
      auto loc(observers_.find(v));
      if (loc != observers_.end()) {
         observers_.erase(loc);
      }
   }

   void changed() {
      if (!observers_.empty()) {
         // Copy observers_ to bector so unObserve works
         ::std::vector<ObserverInterface *> tmp;
         tmp.reserve(observers_.size());
         tmp.assign(observers_.begin(), observers_.end());

         for (auto i(tmp.begin()); i != tmp.end(); ++i) {
            (*i)->observedChanged();
         }
      }
   }

 private:
   obcontainer_t observers_;
};

class Observer : public ObserverInterface {
 public:
   Observer() {}
   Observer(const Observer &) = delete;
   const Observer &operator =(const Observer &b) = delete;
   // g++ does not currently support defaulting the move constructor.
   Observer(Observer &&b) : observed_(b.observed_) {
      b.observed_ = 0;
      return *this;
   }
   // g++ does not currently support defaulting move assignment.
   const Observer &operator =(Observer &&b) {
      observed_ = b.observed_;
      b.observed_ = 0;
      return *this;
   }
   virtual ~Observer() {
      if (observed_) {
         observed_->unObserve(this);
         observed_ = 0;
      }
   }

   virtual void observedChanged() {
      doStuffWith(observed_);
   }
   virtual void observedGoingAway() {
      observed_ = 0;
   }

 private:
   Observed *observed_;

   // Defined elsewhere
   void doStuffWith(Observed *);
};
+2  A: 

There are lots of problems with the code.

  1. Observer::observed_ is left uninitialized in the default constructor, leading to an undefined behavior when the destructor gets called.
  2. No value but 0 is ever assigned to Observer::observed_, making the variable superfluous.
  3. Even if there was a way to associate an observer with an observed, you're not re-registering when moving the observer.
  4. You're trying to return a value from observer's move constructor.
  5. Boost.Signals already solves whatever problem you're trying to solve.
  6. It is more idiomatic to return non-const reference from assignment operators.
avakar
*sigh* Thanks. You've highlighted enough problems that aren't related to what I was thinking of that I think I should delete the question and start over.
Omnifarious
Though, you got one of the problems I was thinking of. #3.
Omnifarious