【问题标题】:Copy constructors for classes with mutual referencing/cyclic dependecies复制具有相互引用/循环依赖关系的类的构造函数
【发布时间】:2015-02-22 00:32:05
【问题描述】:

所以我有一个我认为很常见的涉及对象的 C++ 问题 作品。问题域是这样的:一个动画 gif 可以有很多 框架,而绘制框架取决于 Gif 的其他内容的上下文 帧。

所以我有这个模型(对于这个问题进行了很多简化,但应该 说明):

class Gif {
  std::vector<Frame> _frames;

  // Makes Frame objects from file and puts them in _frames
  Gif(const char* file){...};

  // For clarity, works and copies the _frames member
  Gif(const Gif& other) = default;
};

class Frame {
  Frame(...){
    // Make a frame object
  }
  void draw(const Gif& context){
    // draws self considering context's other frames
  }
};

这可行,但为了简单起见并避免在其中绘制 Frame 错误的上下文,我希望 draw 方法不会采用 context 参数。所以我想用const 创建框架 参考会员_context:

class Frame {
  const Gif& _context;

  // Make a frame object
  Frame(const Gif& context) _context(context){...}

  // Explicit default copy-constructor, for clarity. Breaks horribly
  // when called from Gif's copy constructor, since the new Frame will
  // reference the wrong context, which might be deleted.
  Frame(const& Frame other) = default;


  void draw(){
    // draws self considering _context's other frames
  }
};

这可以编译,但在复制 Gifs 时会严重中断。 FrameGif 的对象是副本,但它们引用 错误的上下文,通常已被删除。

我认为 const 引用成员对于 您打算复制的对象...我应该使用指针和 为自定义Gif 的复制构造函数的主体中的新Frames 显式重置它?

如果有,是什么类型的 指针(原始/智能)?是不是有一个很好的 C++11 技术来制作 这会“自动”发生吗?

或者我应该打破循环依赖以及如何打破?

编辑(感谢 KillianDS):为了清楚起见,您从 gif1 开始,带有一些指向 gif1 的帧(frame1_1,frame1_2,...),现在您想将 gif1 复制到 gif2,并带有复制的帧(frame2_1,frame2_2 ,...) 但那指向 gif2?

【问题讨论】:

  • 为了清楚起见,您从 gif1 开始,带有一些指向 gif1 的帧 (frame1_1, frame1_2, ...),现在您想将 gif1 复制到 gif2 并复制帧 (@ 987654338@) 但指向gif2?
  • 是的@KillianDS,正是
  • Gif 类的用户似乎可以访问Frame 类的对象(引用)。为什么不让Gif 发布某种指针类型FramePointer,其中包含Gif const* contextFrame* frame
  • @dyp 可以复制这个新对象吗?如果是,然后GifFrame 消失怎么办?如果它不可复制,也许这个想法可行,你能把它变成一个答案吗?

标签: c++ c++11


【解决方案1】:

基本上你想让复制的帧指向复制的 gif。这不适用于两个级别的默认复制构造函数,因为您不会盲目复制对象。您的 Gif 复制构造函数可能需要变成这样:

class Gif {
  std::vector<Frame> _frames;

  Gif(const Gif& other) : some_var(other.some_var), _frames(other.frames) ... 
  {
      for(auto& frame: _frames)
      {
          frame.update_context(this);
      }
  }
};

现在的问题是,您在 Frame 类中使用了无法重新安装的引用,因此您无法更新它们。如果您的用例很简单,通常您可以使用裸指针:

class Frame {
    const Gif* _context;

    // Make a frame object
    Frame(const Gif* context) _context(context){...}

    Frame(const& Frame other) = default;
    void update_context(const Gif* context) { _context = context; }
};

为什么是裸指针而不是智能指针:

  1. Frame 没有理由对 Gif 进行内存管理,它只是一个参考。
  2. 您似乎不太可能拥有没有 Gif 上下文的帧。但即使在这种情况下,帧开始时没有 gif,您也可以使用 std::nullptr
  3. 您希望避免使用智能指针,因为您的框架指向您的 gif,而后者“指向”您的框架,而您的框架指向 ...

如果第 2 点不再正确,您何时应该考虑将其设为智能指针(在本例中为 std::weak_ptr)。如果帧以 gif 开头并且 gif 在某处丢失,但帧没有,则使用weak_ptr 更容易,您可以在其中实际检查 gif 上下文是否仍然存在。对于裸指针,您应该始终将指针指向nullptr,否则更容易出错。

【讨论】:

  • 我认为这是可行的。至于第 2 点。这不仅不太可能,而且非常不受欢迎。如果可能的话,我希望编译器禁止我犯这个错误。我现在相信 Frame 本身应该是不可复制的,并且向量应该是 unique_ptr 的向量。
  • 查看我的回答:我更喜欢它,因为它避免了裸指针并且总体上更安全,除非我遗漏了什么。
  • @JoaoTavora 这当然也是一个很好的解决方案,但会产生一个非常不同的 API,其中框架不能单独构造,这不是最初的要求。
  • 真的。在我进行过程中,我发现了“正确”的 API。我不认为有一种方法可以让它们单独构造而不会轻易违反模型的完整性,即最终得到一个引用GifFrame,但Gif没有引用它,或者反之亦然。
【解决方案2】:

所以我对此进行了更多思考,并提出了这个工作示例。 这实际上是第二个版本,第一个使用unique_ptr的和push_back。此版本使用emplace_back 并根据@KillianDS 的建议进行完美转发。要禁止在不通过Gif::add_frame 的情况下创建新框架,还可以使用FrameFriend,建议here

class Gif;
class FrameFriend {
  friend class Gif;
private:
  FrameFriend(){}
};

class Frame {
public:
  int _number;
  const Gif& _context;

  // Constructors are public but a FrameFriend is needed and only Gif
  // can make one. And frames can't be copied or copy-assigned.
  Frame(int number, const Gif& gif, const FrameFriend&) : _number(number),_context(gif) {}
  Frame(Frame&& other) = default;
  Frame operator=(Frame other) = delete;
  Frame(const Frame& another) = delete;

  // ... but they can be constructed from ther frames
  Frame(const Frame& another, const Gif& gif, const FrameFriend&)
    : _number(another._number),
      _context(gif) {}

  void draw(){}
};


class Gif {
  // Each gif owns its frames exclusively
  std::vector<Frame> _frames;

  // Privately, frames can be copied from other gifs
  // calling the appropriate private constructor of Frame
  void copy_frames(const Gif& other){
    _frames.clear();
    _frames.reserve(other._frames.size());
    for (auto& f: other._frames) {
      _frames.emplace_back(f, *this, FrameFriend());
    }
  }

public:
  // Public constructors
  Gif(){};
  // Public constructors. Copying and assigning both call
  // copy_frames() which updates the the gif context
  Gif(const Gif& other) { copy_frames(other); }
  Gif& operator=(Gif& other) { copy_frames(other); return *this;}
  // Move constructor can be the default
  Gif(Gif&& other) = default;

  // Add a frame
  void add_frame(){
    _frames.emplace_back(_frames.size(), *this, FrameFriend());
  }

  // The set of frames can be publicly accessed and frames can even be modified
  // individually, but as no 
  std::vector<Frame>& frames() { return _frames; };
};

【讨论】:

  • 您实际上不再需要任何指针,只需使用常规变量并可能使用emplace_back 来加快速度。我还会在复制代码中使用reserve 以避免不必要的分配。
  • @KillianDS:我不需要指针是什么意思? emplace_backreserve 听起来不错。
  • @KillianDS 我想通了。谢谢!这个过程完全符合我最初的要求,emplace_back 和完美转发看起来就像我正在寻找的 C++11 习语。我可以接受你的回答,但也许你可以编辑提一下这个成语显然更好。
猜你喜欢
  • 2019-05-24
  • 2017-03-03
  • 2011-09-18
  • 2013-10-21
  • 1970-01-01
  • 2011-04-08
  • 1970-01-01
  • 2018-02-26
  • 1970-01-01
相关资源
最近更新 更多