好吧,这真的很难承认,但我确实有一个强烈的诱惑,从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还有其他现实的危险吗?通过说现实,我放弃了一些东西,比如想象一个函数,它接受一个指向向量的指针…

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


当前回答

整个STL被设计成算法和容器是分开的。

这就产生了不同类型迭代器的概念:const迭代器、随机访问迭代器等等。

因此,我建议你接受这个约定,并以这样的方式设计你的算法,即它们不会关心它们正在处理的容器是什么——它们只需要特定类型的迭代器来执行它们的操作。

另外,让我把你引向杰夫·阿特伍德的一些精彩评论。

其他回答

是的,它是安全的,只要你小心不做不安全的事情……我想我从来没有见过有人用向量new,所以在实践中你可能没问题。然而,这并不是c++....中的常用习语

你能提供更多关于算法的信息吗?

有时你在设计上走了一条路,然后看不到你可能走的其他道路——你声称需要用10种新算法来做矢量,这一事实给我敲响了警钟——矢量真的可以实现10种通用算法吗?或者你试图做一个对象,它既是通用矢量,又包含特定的应用程序功能?

我当然不是说你不应该这样做,只是你所提供的信息敲响了警钟,这让我认为也许你的抽象是有问题的,有更好的方法来实现你想要的。

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

您确实需要对任何派生类型进行所有通常的注意,但对于来自标准的基类型的情况并没有什么特别之处。重写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

实际上:如果派生类中没有任何数据成员,就不会有任何问题,甚至在多态使用中也不会。只有在基类和派生类的大小不同和/或有虚函数(即v-table)时,才需要虚析构函数。

但在理论上:来自c++ 0x FCD中的[expr.delete]:在第一个替代方案(删除对象)中,如果要删除的对象的静态类型与其动态类型不同,则静态类型应是要删除的对象的动态类型的基类,并且静态类型应具有虚析构函数或行为未定义。

但是你可以从std::vector中私有地派生而没有问题。 我使用了以下模式:

class PointVector : private std::vector<PointType>
{
    typedef std::vector<PointType> Vector;
    ...
    using Vector::at;
    using Vector::clear;
    using Vector::iterator;
    using Vector::const_iterator;
    using Vector::begin;
    using Vector::end;
    using Vector::cbegin;
    using Vector::cend;
    using Vector::crbegin;
    using Vector::crend;
    using Vector::empty;
    using Vector::size;
    using Vector::reserve;
    using Vector::operator[];
    using Vector::assign;
    using Vector::insert;
    using Vector::erase;
    using Vector::front;
    using Vector::back;
    using Vector::push_back;
    using Vector::pop_back;
    using Vector::resize;
    ...

There is no reason to inherit from std::vector unless one wants to make a class that works differently than std::vector, because it handles in its own way the hidden details of std::vector's definition, or unless one has ideological reasons to use the objects of such class in place of std::vector's ones. However, the creators of the standard on C++ did not provide std::vector with any interface (in the form of protected members) that such inherited class could take advantage of in order to improve the vector in a specific way. Indeed, they had no way to think of any specific aspect that might need extension or fine-tune additional implementation, so they did not need to think of providing any such interface for any purpose.

The reasons for the second option can be only ideological, because std::vectors are not polymorphic, and otherwise there is no difference whether you expose std::vector's public interface via public inheritance or via public membership. (Suppose you need to keep some state in your object so you cannot get away with free functions). On a less sound note and from the ideological point of view, it appears that std::vectors are a kind of "simple idea", so any complexity in the form of objects of different possible classes in their place ideologically makes no use.

整个STL被设计成算法和容器是分开的。

这就产生了不同类型迭代器的概念:const迭代器、随机访问迭代器等等。

因此,我建议你接受这个约定,并以这样的方式设计你的算法,即它们不会关心它们正在处理的容器是什么——它们只需要特定类型的迭代器来执行它们的操作。

另外,让我把你引向杰夫·阿特伍德的一些精彩评论。