获取模板以便与C ++中的接口使用unique_ptr

时间:2015-03-20 17:36:26

标签: c++11 inheritance interface unique-ptr dynamic-cast

首先,没有像“界面”这样的内置概念。通过C ++中的接口,我的意思是一些看起来像的抽象基类:

struct ITreeNode
{
   ... // some pure virtual functions
};

然后我们可以使用实现接口的具体结构,例如:

struct BinaryTreeNode : public ITreeNode
{
   BinaryTreeNode* LeftChild;
   BinaryTreeNode* RightChild;

// plus the overriden functions
};

这很有道理:ITreeNode是一个界面;并非每个实施都有Left& Right个孩子 - 仅BinaryTreeNode

为了使事情可以广泛重用,我想写一个模板。因此,ITreeNode必须为ITreeNode<T>,而BinaryTreeNode必须为BinaryTreeNode<T>,如下所示:

template<typename T>
struct BinaryTreeNode : public ITreeNode<T>
{
};

为了让事情变得更好,让我们使用独特的指针(智能点更常见,但我知道解决方案 - dynamic_pointer_cast)。

template<typename T>
struct BinaryTreeNode : public ITreeNode<T>
{
   typedef std::shared_ptr<BinaryTreeNode<T>> SharedPtr;
   typedef std::unique_ptr<BinaryTreeNode<T>> UniquePtr;
   // ... other stuff
};

同样地,

template<typename T>
struct ITreeNode
{
   typedef std::shared_ptr<ITreeNode<T>> SharedPtr;
   typedef std::unique_ptr<ITreeNode<T>> UniquePtr;
};

这一切都很好,直到这一点: 我们现在假设我们需要编写一个BinaryTree类。

有一个函数插入,它接受一个值T并使用某种算法将其插入根节点(当然它将是递归的)。

为了使函数可测试,可模拟并遵循良好实践,参数需要是接口,而不是具体的类。 (让我们说这是一个严格的规则,不能被打破。)

template<typename T>
void BinaryTree<T>::Insert(const T& value, typename ITreeNode<T>::UniquePtr& ptr)
{
   Insert(value, ptr->Left); // Boooooom, exploded
   // ...
}

这是问题所在: Left不是ITreeNode的字段!最糟糕的是,你无法将unique_ptr<Base>投射到unique_ptr<Derived>

这样的场景的最佳做法是什么?

非常感谢!

1 个答案:

答案 0 :(得分:4)

好的,过度工程了!但请注意,在大多数情况下,这种低级数据结构可以从透明度和简单的内存布局中获益。将抽象级别放在容器上方可以显着提升性能。

template<class T>
struct ITreeNode {
  virtual void insert( T const & ) = 0;
  virtual void insert( T      && ) = 0;

  virtual T const* get() const = 0;
  virtual T      * get()       = 0;
  // etc
  virtual ~ITreeNode() {}
};

template<class T>
struct IBinaryTreeNode : ITreeNode<T> {
  virtual IBinaryTreeNode<T> const* left()  const = 0;
  virtual IBinaryTreeNode<T> const* right() const = 0;

  virtual std::unique_ptr<IBinaryTreeNode<T>>& left()  = 0;
  virtual std::unique_ptr<IBinaryTreeNode<T>>& right() = 0;

  virtual void replace(T const &) = 0;
  virtual void replace(T      &&) = 0;
};

template<class T>
struct BinaryTreeNode : IBinaryTreeNode<T> {
  // can be replaced to mock child creation:
  std::function<std::unique_ptr<IBinaryTreeNode<T>>()> factory
    = {[]{return std::make_unique<BinaryTreeNode<T>>();} };

  // left and right kids:
  std::unique_ptr<IBinaryTreeNode<T>> pleft;
  std::unique_ptr<IBinaryTreeNode<T>> pright;

  // data.  I'm allowing it to be empty:
  std::unique_ptr<T> data;

  template<class U>
  void insert_helper( U&& t ) {
    if (!get()) {
      replace(std::forward<U>(t));
    } else if (t < *get()) {
      if (!left()) left() = factory();
      assert(left());
      left()->insert(std::forward<U>(t));
    } else {
      if (!right()) right() = factory();
      assert(right());
      right()->insert(std::forward<U>(t));
    }
  }
  // not final methods, allowing for balancing:
  virtual void insert( T const&t ) override { // NOT final
    return insert_helper(t);
  }
  virtual void insert( T &&t ) override { // NOT final
    return insert_helper(std::move(t));
  }
  // can be empty, so returns pointers not references:
  T const* get() const override final {
    return data.get();
  }
  T      * get()       override final {
    return data.get();
  }
  // short, could probably skip:
  template<class U>
  void replace_helper( U&& t ) {
    data = std::make_unique<T>(std::forward<U>(t));
  }
  // only left as customization points if you want.
  // could do this directly:
  virtual void replace(T const & t) override final {
    replace_helper(t);
  }
  virtual void replace(T      && t) override final  {
    replace_helper(std::move(t));
  }
  // Returns pointers, because no business how we store it in a const
  // object:
  virtual IBinaryTreeNode<T> const* left()  const final override {
    return pleft.get();
  }
  virtual IBinaryTreeNode<T> const* right() const final override {
    return pright.get();
  }
  // returns references to storage, because can be replaced:
  // (could implement as getter/setter, but IBinaryTreeNode<T> is
  // "almost" an implementation class, some leaking is ok)
  virtual std::unique_ptr<IBinaryTreeNode<T>>& left() final override {
    return pleft;
  }
  virtual std::unique_ptr<IBinaryTreeNode<T>>& right() final override {
    return pright;
  }
};