C++0x 移动构造函数陷阱

发布于 2024-08-19 09:05:00 字数 3149 浏览 7 评论 0原文

编辑:我在这里重新提出了同样的问题(在解决了该问题指出的问题之后):为什么这个 C++0x 程序会生成意外的输出?

基本思想是,如果不小心,指向可移动的东西可能会带来一些奇怪的结果。


C++ 移动构造函数和移动赋值运算符看起来确实是积极的事情。它们可以用在复制构造函数没有意义的情况下,因为它们不需要指向重复的资源。

但有些情况下,如果你不小心,它们就会咬你。这尤其重要,因为我已经看到了允许编译器生成移动构造函数的默认实现的建议。如果有人能给我一个链接,我将提供一个链接。

因此,这里有一些代码存在一些可能不完全明显的缺陷。我测试了代码以确保它可以使用 -std=gnuc++0x 标志在 g++ 中编译。这些缺陷是什么?您将如何解决它们?

#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 *);
};

Edit: I re-asked this same question (after fixing the problems noted with this question) here: Why does this C++0x program generates unexpected output?

The basic idea is that pointing to moveable things may net you some odd results if you aren't careful.


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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

月牙弯弯 2024-08-26 09:05:00

代码有很多问题。

  1. Observer::observed_ 在默认构造函数中未初始化,导致调用析构函数时出现未定义的行为。
  2. 除了 0 之外,没有任何值被分配给 Observer::observed_,使得该变量变得多余。
  3. 即使有一种方法将观察者与被观察者关联起来,在移动观察者时也不会重新注册。
  4. 您试图从观察者的移动构造函数返回一个值。
  5. Boost.Signals 已经解决了您遇到的任何问题试图解决。
  6. 从赋值运算符返回非常量引用更为惯用。

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.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文