关于C++:GCcc4.92的STBD的LBSTDC + +实现::向量继承自Y向量基(非虚拟DestuCor)。为什么这样可以?

gcc4.9.2's libstdc++ implementation of std::vector inherits from _Vector_base (non-virtual destuctor). Why is this OK?

本问题已经有最佳答案,请猛点这里访问。

所以我已经使用了一段时间从std::vector派生的容器。也许这是一个糟糕的设计决策,有几个原因,关于您是否应该做这样的事情的问题,我们在这里进行了广泛的讨论:

不能从std::vector继承

子类/继承标准容器?

是否有来自C++ STL容器的实际风险?

是否可以从STL容器继承实现,而不是委托?

我确信我错过了一些讨论……但是在链接中可以找到两种观点的合理论据。据我所知,"因为~向量()是非虚的"是"规则"的基础,您不应该从STL容器继承。但是,如果我查看G++4.9.2中std::vector的实现,我发现std::vector继承自_vector_base,而_vector_base是一个非虚拟析构函数。

1
2
3
4
5
6
7
8
9
10
template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector : protected _Vector_base<_Tp, _Alloc>
{
...
  ~vector() _GLIBCXX_NOEXCEPT
  { std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
    _M_get_Tp_allocator()); }

...
}

哪里:

1
2
3
4
5
6
7
8
9
10
template<typename _Tp, typename _Alloc>
struct _Vector_base
{
...
  ~_Vector_base() _GLIBCXX_NOEXCEPT
  { _M_deallocate(this->_M_impl._M_start, this->_M_impl._M_end_of_storage
    - this->_M_impl._M_start); }

...
}

因此,std::vector的gcc 4.9.2实现继承自具有非虚拟析构函数的基类。这使我相信这是一种可以接受的做法。为什么这样可以?这种做法不危险的具体条件是什么?


1:std::vector不继承_Vector_base的遗产

或者更确切地说,不是在我的标准库实现中。libc++实现类似矢量的操作,因此:好的。

1
2
3
4
5
6
7
namespace std
{

template <class T, class Allocator = allocator<T> >
class vector
{
...

当然,您的实现可能继承自基类,但我的实现没有继承。这会导致你对"规则"的第一个错误解释:好的。

C++标准库可以以多种方式实现,并且我们不能从它的一个实现中对标准库(由EDCOX1(2)定义的一个)作出广泛、彻底的假设或声明。好的。

所以,在我们进一步讨论之前,让我们记住:在这个答案中,我们不会只关注一个实现。我们将考虑所有的实现(通过关注C++标准中的定义;而不是硬盘驱动器上特定的头文件的定义)。好的。2:好吧,是的,所以std::vector继承了_Vector_base的遗产。

但在我们继续之前,让我们承认您的实现可能继承自_Vector_base,这没关系。那么,为什么允许std::vector从基类继承,但不允许您从std::vector继承?好的。3:如果你愿意的话,你可以继承std::vector(小心点)

std::vector可以从_Vector_base继承,原因与你从std::vector继承的原因相同:图书馆的实施者非常谨慎(你也应该如此)。好的。

std::vector不是具有_Vector_base指针的deleted多态性。所以我们不必担心~vector()不是虚拟的。如果您没有使用多态的std::vector指针来继承继承类delete,那么~vector()是非虚拟的就不是问题了。很好。别担心。我们不要为此大惊小怪。没有多态的delete意味着我们不必担心析构函数是否是虚拟的。好的。

但除此之外,库的实现者还确保了从使用_Vector_base引用中不分割std::vector。对你也是一样:如果你能确保你的自定义向量类永远不会被切片(由某人使用std::vector引用),那么你在这里也可以。不分片意味着我们不必担心复制品质量不好。好的。4:为什么你不应该从江户那里继承呢?

在其他问题中,这个问题已经得到了很好的回答,但我还是要在这里再次说明(并重点讨论整个_Vector_base继承问题):你(可能)不应该继承std::vector的遗产。好的。

问题是,如果这样做,使用自定义向量类的人可能会多态地删除它(他们可能有指向它的std::vector指针,而delete指针,如果这样做,这是一件糟糕的事情)。或者他们可能有一个std::vector引用您的自定义对象,并试图复制它(它将对对象进行切片,这也可能是一件坏事)(我一直假设在复制时需要对您的自定义对象的std::vector引用,因为我一直假设您足够小心,从不访问使用不小心的赋值或函数调用对对象进行计数切片;我敢肯定,您不会这么粗心,但使用std::vector引用的其他人可能是(是的,我在这里有点开玩笑)。好的。

你可以控制你用代码做什么。如果你能(小心地)控制它以确保没有多态的delete或对象切片,你就没事了。好的。

但是有时候你不能真正控制别人对你的代码做什么。如果你在一个团队中工作,如果一个团队成员无意中做了这些事情,这可能会有问题。这就是为什么我一直提出"小心"的问题。好的。

更糟糕的是,如果你的代码被客户机使用,你真的无法控制他们的行为,如果他们做了其中的一件坏事,你可能会被指责,并被责成去修复它(有趣的是,重构你以前依赖于从std::vector继承的自定义类的所有代码)(或者告诉客户他们不能这样做)。不会,但你可能会遇到一个脾气暴躁的客户,他们浪费时间调试一个他们没想到会遇到的奇怪问题)。好的。

但是,C++标准库实现者可以逃脱这种继承,因为它们可以很好地控制事物:没有人允许使用EDCOX1(0)。您可以使用std::vector。仅限于std::vector。由于您不允许(曾经)使用_Vector_base,标准库实现人员不必担心对象切片或多态delete,而且由于他们在受控环境中的实现非常小心,所以一切都很顺利。好的。

但更好的是,通过不假设std::vector是如何实现的,而将其视为(有用的)黑盒,我们可以确保使用std::vector的方式可移植到其他标准库实现中。如果您假设std::vector继承自某个基类,那么您将限制代码的可移植性。好的。好啊。


只是一个数据点。

在"C++巡游"中,Bjarne Stroustrup定义了一个类模板,该模板是从EDCOX1 22派生的。其他一切都委托给基类。


因为用户代码试图破坏_Vector_base是非法的,因为它是stdlib内部类型。这样可以防止析构函数出现任何问题。你也不能这么说。

简单地说,标准库的内部结构是一种特殊的情况,无论是在语言规则中,还是在对它们来说合理的情况下。您不能从它们对您自己的代码所做的工作中进行归纳。


答案有两部分:

  • 因为_Vector_base知道它会被std::vector继承,所以设计成这样

  • 每一条规则都有例外。如果继承std::vector对你来说是有意义的,那么就做有意义的事情。


  • 规则"从不从没有虚拟析构函数的类型继承"试图解决的问题是:

    • 无论何时,当你在一个没有虚拟析构函数的对象中使用delete时,被调用的析构函数并不依赖于动态类型。也就是说,如果您的delete所声明的指针类型与对象的动态类型不同,则会调用错误的析构函数。

    禁止没有虚拟析构函数的类型的子类化等同于将这些类型限制为独立的类:没有父类和派生子类的类。显然,指向这些类之一的指针的声明类型永远不能与对象的动态类型不同,因此调用错误的析构函数没有危险。

    然而,这条规则有点过于严格:需要避免的是调用错误的析构函数,而不是在本身或其内部进行子类化。以下是不允许调用错误析构函数的类层次结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Foo {
        protected:
            Foo();
            ~Foo();
    };

    class Bar : public Foo {
        public:
            Bar();
            ~Bar();
    };

    在这两个类中,完全可以创建一个Bar对象,并通过Foo指针或引用对其进行操作,但是,不能使用Foo*来销毁该对象—类Foo本身不可从其他代码中恢复或销毁。

    现在回到std::vector<>。类std::vector<>是打算使用的类,它没有虚拟析构函数。但这并不要求std::vector<>没有基类。一个库可以按照上述FooBar的模式,通过将其实现划分为两个类来自由地实现std::vector<>。唯一需要避免的是,用户使用基类指针来销毁派生对象。当然,_Vector_base类型是标准库实现的私有类型,用户不应该使用它,所以没关系。

    然而,子类化std::vector<>是一个完全不同的情况:std::vector<>的析构函数是公共的,因此不能阻止类的用户。

    1
    2
    3
    template <class T> class MyVector : public std::vector<T> {
        ...
    };

    要使用基类指针,他们可能会获取销毁MyVector<>。您可以使用私有继承或受保护继承来避免这个问题,但这并不能提供公共继承的好处。所以,是的,你不应该将std::vector<>划分为子类,即使std::vector<>从另一个私有类继承是完全可以的。


    没有这样的规则"不要从具有非虚拟析构函数的基类继承"。如果有一个规则,它将是:"如果您甚至有一个虚拟方法,使您的析构函数为虚拟的,这也意味着不要从具有非虚拟析构函数的基类继承"。imho如果遵循此操作,可以继承stl容器。

    似乎有些编译器架构师也同意这一点。字面上有一个警告,说明它-供参考:什么是'有虚拟方法…但是C++编译过程中非虚析构函数的告警意味着什么?