好吧,这真的很难承认,但我确实有一个强烈的诱惑,从std::vector继承。

I need about 10 customized algorithms for vector and I want them to be directly members of the vector. But naturally I want also to have the rest of std::vector's interface. Well, my first idea, as a law-abiding citizen, was to have an std::vector member in MyVector class. But then I would have to manually reprovide all of the std::vector's interface. Too much to type. Next, I thought about private inheritance, so that instead of reproviding methods I would write a bunch of using std::vector::member's in the public section. This is tedious too actually.

在这里,我真的认为我可以简单地从std::vector公开继承,但在文档中提供了一个警告,该类不应该多态地使用。我认为大多数开发人员都有足够的能力来理解这无论如何都不应该多态地使用。

我的决定是绝对不合理的吗?如果有,为什么?你能提供一个替代方案,这将有额外的成员实际上成员,但不会涉及重新键入所有矢量的接口?我很怀疑,但如果你能做到,我会很开心的。

而且,除了有些白痴能写出这样的东西

std::vector<int>* p  = new MyVector

使用MyVector还有其他现实的危险吗?通过说现实,我放弃了一些东西,比如想象一个函数,它接受一个指向向量的指针…

我已经陈述了我的观点。我有罪。现在就看你是否原谅我了:)


当前回答

我认为很少有规则是应该百分之百地盲从的。听起来你已经考虑了很多,并且相信这是一条正确的道路。所以——除非有人提出很好的具体理由不这样做——我认为你应该继续你的计划。

其他回答

这个问题肯定会让人紧张得喘不过气来,但事实上,没有理由避免从标准容器派生,或者“不必要地增加实体”。最简单、最短的表达是最清晰、最好的。

您确实需要对任何派生类型进行所有通常的注意,但对于来自标准的基类型的情况并没有什么特别之处。重写base成员函数可能很棘手,但对于任何非虚基来说都是不明智的,因此这里没有太多特别之处。如果要添加一个数据成员,如果该成员必须与基库的内容保持一致,则需要考虑切片问题,但这同样适用于任何基库。

The place where I have found deriving from a standard container particularly useful is to add a single constructor that does precisely the initialization needed, with no chance of confusion or hijacking by other constructors. (I'm looking at you, initialization_list constructors!) Then, you can freely use the resulting object, sliced -- pass it by reference to something expecting the base, move from it to an instance of the base, what have you. There are no edge cases to worry about, unless it would bother you to bind a template argument to the derived class.

在c++ 20中,这种技术将立即发挥作用的地方是预留。我们可能在哪里写过

  std::vector<T> names; names.reserve(1000);

我们可以说

  template<typename C> 
  struct reserve_in : C { 
    reserve_in(std::size_t n) { this->reserve(n); }
  };

然后,即使作为班级成员,

  . . .
  reserve_in<std::vector<T>> taken_names{1000};  // 1
  std::vector<T> given_names{reserve_in<std::vector<T>>{1000}}; // 2
  . . .

(根据首选项),而不需要编写构造函数来调用reserve()。

(The reason that reserve_in, technically, needs to wait for C++20 is that prior Standards don't require the capacity of an empty vector to be preserved across moves. That is acknowledged as an oversight, and can reasonably be expected to be fixed as a defect in time for '20. We can also expect the fix to be, effectively, backdated to previous Standards, because all existing implementations actually do preserve capacity across moves; the Standards just haven't required it. The eager can safely jump the gun -- reserving is almost always just an optimization anyway.)

有些人会认为,reserve_in的情况最好由一个免费的函数模板来实现:

  template<typename C> 
  auto reserve_in(std::size_t n) { C c; c.reserve(n); return c; }

这样的替代方案当然是可行的——有时甚至会因为RVO而快得无限大。但是推导或自由函数的选择应该根据其本身的优点,而不是根据对从标准组件推导的毫无根据的迷信。在上面的例子中,只有第二种形式可以使用free函数;尽管在类上下文之外,它可以写得更简洁一点:

  auto given_names{reserve_in<std::vector<T>>(1000)}; // 2

我最近也继承了std::vector,发现它非常有用,到目前为止我还没有遇到任何问题。

我的类是一个稀疏矩阵类,这意味着我需要在某个地方存储我的矩阵元素,即在std::vector中。我继承的原因是我有点太懒了,不愿意为所有的方法编写接口,而且我正在通过SWIG将类接口到Python,其中已经有std::vector的良好接口代码。我发现将这个接口代码扩展到我的类中要比从头开始编写一个新代码容易得多。

这种方法的唯一问题不是非虚析构函数,而是一些我想重载的其他方法,比如push_back()、resize()、insert()等。私人继承的确是一个不错的选择。

谢谢!

实际上,std::vector的公共继承并没有什么问题。如果你需要这个,就去做那个。

我建议只有在确实有必要的时候才这样做。只有当你不能用自由函数做你想做的事情时(例如,应该保持一些状态)。

问题是MyVector是一个新实体。这意味着一个新的c++开发人员在使用它之前应该知道它到底是什么。std::vector和MyVector之间的区别是什么?哪一个更适合在这里和那里使用?如果我需要移动std::vector到MyVector怎么办?我可以只用swap()吗?

不要仅仅为了让某些东西看起来更好而创造新的实体。这些实体(尤其是如此常见的实体)不会生活在真空中。它们将生活在熵不断增加的混合环境中。

如果你正在考虑这个问题,那么你显然已经把办公室里的语言书呆子都干掉了。有了他们,为什么不直接做呢

struct MyVector
{
   std::vector<Thingy> v;  // public!
   void func1( ... ) ; // and so on
}

这将避免所有可能出现的错误,可能会意外地向上转换你的MyVector类,你仍然可以访问所有的向量操作,只需添加一个小.v。

在这里,让我再介绍两种做你想做的事的方法。一种是包装std::vector的另一种方法,另一种是继承而不给用户破坏任何东西的方法:

让我添加另一种包装std::vector的方式,而无需编写大量的函数包装器。

#include <utility> // For std:: forward struct Derived: protected std::vector<T> { // Anything... using underlying_t = std::vector<T>; auto* get_underlying() noexcept { return static_cast<underlying_t*>(this); } auto* get_underlying() const noexcept { return static_cast<underlying_t*>(this); } template <class Ret, class ...Args> auto apply_to_underlying_class(Ret (*underlying_t::member_f)(Args...), Args &&...args) { return (get_underlying()->*member_f)(std::forward<Args>(args)...); } }; Inheriting from std::span instead of std::vector and avoid the dtor problem.