复制具有相互引用/循环依赖性的类的构造函数

时间:2015-02-20 12:24:46

标签: c++ c++11

所以我认为这是一个非常常见的涉及对象的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
  }
};

这会编译但在复制Gif时可怕地破坏。 FrameGif的对象是副本,但它们引用了 错误的上下文,通常已被删除。

我认为const参考成员可能是一个坏主意 你打算复制的对象......我应该使用指针吗? 为自定义Frame复制构造函数体内的新Gif明确重置它?

如果是这样,那是什么样的 指针(原始/智能)?是不是有一个很好的C ++ 11制作技术 这种情况发生了#34;自动&#34;?

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

编辑(感谢KillianDS):要清楚,你从gif1开始,有一些框架(frame1_1,frame1_2,...)指向gif1,现在你想用复制的框架将gif1复制到gif2(frame2_1,frame2_2 ,...)但是那指向gif2?

2 个答案:

答案 0 :(得分: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. 即可
  4. 你想避免使用智能指针,因为你的框架指向你的Gif指点&#39;指向...的框架
  5. 你应该在什么时候认为这是一个智能指针(在这种情况下为std::weak_ptr),如果第2点不再正确。如果一个框架以gif开头且gif在某个地方丢失,但框架没有,那么使用weak_ptr更容易,你可以实际检查gif上下文是否仍然存在。使用裸指针时,您应始终将指针指向nullptr,否则这更容易出错。

答案 1 :(得分:0)

所以我想到了更多关于这一点,并来到这个工作的例子。 这实际上是第二个版本,第一个使用unique_ptrpush_back。此版本使用emplace_back并根据@ KillianDS的建议完美转发。禁止在不经过Gif::add_frame的情况下创建新框架也会使用建议hereFrameFriend

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