你不能继承std :: vector

Arm*_*yan 182 c++ oop inheritance stl vector

好吧,这真的很难承认,但我现在确实有很强的诱惑力来继承std::vector.

我需要大约10个定制的矢量算法,我希望它们直接成为矢量的成员.但我自然也希望拥有剩下std::vector的界面.好吧,作为一个守法的公民,我的第一个想法是std::vectorMyVector课堂上有一个成员.但是我必须手动重新编写所有std :: vector的接口.打字太多了.接下来,我考虑了私有继承,所以我不会using std::vector::member在公共部分写一些方法而不是重新提取方法.实际上这也很乏味.

在这里,我确实认为我可以简单地从公开继承std::vector,但在文档中提供警告,不应该多态地使用此类.我认为大多数开发人员都有足够的能力去理解这不应该以多态方式使用.

我的决定绝对没有道理吗?如果是这样,为什么?你能提供一个替代方案,其他成员实际上是成员,但不会涉及重新输入所有vector的界面吗?我对此表示怀疑,但如果可以,我会很高兴.

此外,除了一些白痴可以写类似的事实

std::vector<int>* p  = new MyVector
Run Code Online (Sandbox Code Playgroud)

使用MyVector 有任何其他现实危险吗?通过说现实,我放弃像想象一个带有指向矢量的指针的函数...

好吧,我已经陈述了我的情况.我犯罪了.现在由你来原谅我了不起:)

Sta*_*tas 151

实际上,公共继承没有任何问题std::vector.如果你需要这个,那就去做吧.

我建议只有在真的有必要时这样做.只有你不能用自由功能做你想做的事情(例如应该保持一些状态).

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

不要生产新的实体只是为了让事情看起来更好.这些实体(尤其是这种实体)不会生活在真空中.他们将生活在混合环境中,不断增加熵.

  • `std :: vector`的析构函数不是`virtual`,因此你永远不应该继承它 (11认同)
  • 我唯一的反驳就是必须真正知道他在做什么才能做到这一点.例如,*不要*将其他数据成员引入`MyVector`,然后尝试将其传递给接受`std :: vector&`或`std :: vector*`的函数.如果使用std :: vector*或std :: vector&涉及任何类型的复制赋值,我们就会解决"MyVector"的新数据成员不会被复制的问题.通过基指针/引用调用swap也是如此.我倾向于认为任何一种存在对象切片风险的继承层次结构都是不好的. (7认同)
  • @GrahamAsher 不,每当您通过指向 base 的指针删除任何对象而没有虚拟析构函数时,这是标准下的未定义行为。我理解你认为正在发生的事情;你只是碰巧错了。“基类析构函数被调用,它可以工作”是这种未定义行为的一种可能的症状(也是最常见的),因为这是编译器通常生成的幼稚机器代码。这*不安全*也不是一个好主意。 (5认同)
  • @GrahamAsher 如果您通过指向基类的指针删除对象,并且析构函数不是虚拟的,则您的程序表现出未定义的行为。未定义行为的一种可能结果是“它在我的测试中运行良好”。另一个是它通过电子邮件向您的祖母发送您的网络浏览历史记录。两者都符合 C++ 标准。它随着编译器、操作系统或月相的点发布而从一个变为另一个也是合规的。 (3认同)
  • @graham C++ 不是由它生成的汇编代码定义的。标准清晰、完整且定义规范;它定义了什么是 C++。如果您想更改标准,请提出建议。在此之前,您的代码具有明确且明确未由标准定义的行为。我得到它。认为 C++ 是由它生成的代码定义的,这是一个常见的错误。但是直到你理解了这个根本性的错误,当 `((int)(unsigned)(int)-1) &gt;= 0` 被优化为 `true` 和无数其他事情时,你会继续被搞砸并可能会生气. 包括这个错误。 (3认同)
  • 由于以下原因,我创建了一个公开继承std :: vector的类:我有一个带有非STL矢量类的旧代码,并且我想转到STL。我将旧类重新实现为std :: vector的派生类,使我可以继续使用旧代码中的旧函数名(例如Count()而不是size()),同时使用std :: vector编写新代码功能。我没有添加任何数据成员,因此std :: vector的析构函数对于在堆上创建的对象可以正常工作。 (2认同)
  • @andre,您不应在堆上使用 new 分配 std 向量,也不应通过原始指针删除它;您不得对向量使用唯一的 ptr。基于指针的所有权需要虚拟 dtor;其他所有权语义则不然。 (2认同)
  • @GrahamAsher http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4713.pdf [删除] 8.5.2.5/3:“在单对象删除表达式中,如果静态待删除对象的类型与其动态类型不同,静态类型应为待删除对象动态类型的基类,且静态类型应具有虚析构函数或行为未定义”。——它甚至不是由遗漏定义的。这是明确的未定义行为。C++ 程序的含义**不是由它生成的程序集定义**的。 (2认同)

Kos*_*Kos 90

整个STL的设计方式使得算法和容器是分开的.

这导致了不同类型的迭代器的概念:const迭代器,随机访问迭代器等.

因此,我建议你接受这个约定,并设计你的算法,使他们不关心他们正在处理的容器是什么 - 他们只需要一个特定类型的迭代器,他们需要执行他们的操作.

另外,让我转发一下Jeff Attwood的一些好评.


Bas*_*evs 56

std::vector公开继承的主要原因是缺少虚拟析构函数,有效地阻止了后代的多态使用.特别是,你都不准delete一个std::vector<T>*实际指向的派生类对象(即使在派生类中不会增加成员),但编译器一般不能向您发出警告.

在这些条件下允许私有继承.因此,我建议使用私有继承并从父进程转发所需的方法,如下所示.

class AdVector: private std::vector<double>
{
    typedef double T;
    typedef std::vector<double> vector;
public:
    using vector::push_back;
    using vector::operator[];
    using vector::begin;
    using vector::end;
    AdVector operator*(const AdVector & ) const;
    AdVector operator+(const AdVector & ) const;
    AdVector();
    virtual ~AdVector();
};
Run Code Online (Sandbox Code Playgroud)

您应该首先考虑重构您的算法以抽象它们正在操作的容器类型,并将它们保留为免费模板化函数,正如大多数回答者所指出的那样.这通常通过使算法接受一对迭代器而不是容器作为参数来完成.

  • `vector` 的分配存储不是问题——毕竟,`vector` 的析构函数可以通过指向 `vector` 的指针调用。只是标准禁止通过基类表达式“删除”*自由存储对象*。原因肯定是(de)分配机制可能会尝试推断内存块的大小以从`delete`的操作数中释放出来,例如当某些大小的对象有多个分配区域时。该限制不适用于具有静态或自动存储持续时间的对象的正常销毁。 (2认同)

Cra*_*rks 34

如果你正在考虑这个问题,你显然已经杀死了你办公室里的语言学生.随着他们走开,为什么不做

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

这将避开可能由于意外地向上转换MyVector类而导致的所有可能的错误,并且您仍然可以通过添加一点来访问所有向量操作.v.


Kar*_*tel 19

你有什么希望完成的?只提供一些功能?

C++惯用的方法是编写一些实现该功能的免费函数.有可能你真的不需要std :: vector,特别是你正在实现的功能,这意味着你实际上通过尝试继承std :: vector而失去了可重用性.

我强烈建议您查看标准库和标题,并思考它们的工作原理.

  • 在外部算法中很难缓存繁重操作的结果.假设您必须计算向量中所有元素的总和,或者用向量元素作为系数求解多项式方程.那些操作很重,懒惰会对他们有用.但是如果没有包装或继承容器,就无法引入它. (17认同)
  • @Armen:更好的美学和更高的通用性,也将提供免费的"前"和"后"功能.:)(还要考虑C++ 0x和boost中自由`begin`和`end`的例子.) (12认同)
  • @snemarch:美学*是*一个很好的理由:) (10认同)
  • @Armen:除了美学外,还有什么*好*的理由? (6认同)
  • 我不相信.你能用一些提议的代码更新来解释原因吗? (5认同)
  • 我仍然不知道免费功能有什么问题.如果你不喜欢STL的"美学",那么在美学上,C++可能是错误的.并且添加一些成员函数将无法修复它,因为许多其他算法仍然是免费函数. (3认同)
  • 看,例如,除了back()和front()我还想提供middle(),我不希望它看起来像middle(v),但是v.middle() (2认同)
  • @Armen:不,美学不是一个好理由.代码可读性和一致性是很好的理由.但它不必看起来"漂亮".它必须清晰可读.当然,美学是主观的.无论如何,正如所建议的,获得一致语法的更好方法是将成员(`front()`,`back()`和其他任何东西)重新实现为非成员.在我的代码中,我定义了自由的`begin()`和`end()`函数,部分原因是为了更一致的语法,部分原因是因为我可以将它们与数组一起使用. (2认同)
  • @Basilevs的评论是我听过添加方法而不是免费功能的第一个好理由. (2认同)
  • @Basilevs:在我看到你的评论之前,我几乎已经确信外部比内部更好.如果一个人坚持要有外部通用功能,那么必须设计一个缓存策略和合同:) (2认同)

NPE*_*NPE 14

我认为在100%的时间里应该盲目遵循很少的规则.听起来你已经给了它很多想法,并且确信这是要走的路.所以 - 除非有人提出不这样做的具体原因 - 我认为你应该继续你的计划.

  • 你的第一句话在100%的时间都是真的.:) (7认同)
  • 不幸的是,第二句话不是.他没有多想过.大多数问题都无关紧要.显示他动机的唯一部分是"我希望他们成为矢量的直接成员".我想要.没有理由*为什么*这是可取的.这听起来像是他给了它*根本没想过*. (5认同)

小智 7

没有理由继承,std::vector除非有人想要创建一个不同于std::vector它的类,因为它以自己的方式处理std::vector定义的隐藏细节,或者除非有意识形态的理由使用这类的对象代替std::vector的.但是,C++标准的创建者没有提供std::vector任何接口(以受保护成员的形式),这样的继承类可以利用这些接口以特定方式改进向量.实际上,他们没有办法考虑可能需要扩展或微调其他实现的任何特定方面,因此他们不需要考虑为任何目的提供任何此类接口.

第二种选择的原因只能是意识形态的,因为std::vectors不是多态的,否则无论是std::vector通过公共继承还是通过公共成员公开公共接口都没有区别.(假设您需要在对象中保留一些状态,这样您就无法使用自由函数).从一个不那么合理的角度来看,从意识形态的角度来看,似乎std::vectors是一种"简单的想法",因此在意识形态上不同可能类别的对象形式的任何复杂性都没有用处.