从C++ STL容器中获取是否存在任何真正的风险?

Tho*_*day 30 c++ inheritance stl

声称使用标准C++容器作为基类是错误的说法让我感到惊讶.

如果没有滥用语言来宣布......

// Example A
typedef std::vector<double> Rates;
typedef std::vector<double> Charges;
Run Code Online (Sandbox Code Playgroud)

......那么确切地说,宣告的危险是什么......

// Example B
class Rates : public std::vector<double> { 
    // ...
} ;
class Charges: public std::vector<double> { 
    // ...
} ;
Run Code Online (Sandbox Code Playgroud)

B的积极优势包括:

  • 启用函数重载,因为f(Rates&)和f(Charges&)是不同的签名
  • 允许其他模板专用,因为X <Rates>和X <Charges>是不同的类型
  • 前瞻性声明是微不足道的
  • 调试器可能会告诉您对象是费率还是费用
  • 如果随着时间的推移,费率和费用会产生个性 - 一个单一的费率,费用的输出格式 - 这个功能有明显的实施范围.

A的积极优势包括:

  • 不必提供构造函数等的简单实现
  • 十五年前的预标准编译器是唯一能够编译遗产的编译器
  • 由于专业化是不可能的,模板X <费率>和模板X <费用>将使用相同的代码,因此没有毫无意义的膨胀.

这两种方法都优于使用原始容器,因为如果实现从vector <double>更改为vector <float>,那么只有一个地方可以用B更改,也许只有一个地方可以用A更改(可能更多,因为有人可能在多个地方放置了相同的typedef语句.

我的目标是这是一个具体的,可回答的问题,而不是对更好或更差实践的讨论.显示由于从标准容器派生而可能发生的最糟糕的事情,这可以通过使用typedef来防止.

编辑:

毫无疑问,向类Rate或类Charges添加析构函数会有风险,因为std :: vector不会将其析构函数声明为虚拟.示例中没有析构函数,也不需要析构函数.销毁Rates或Charges对象将调用基类析构函数.这里也不需要多态性.挑战在于使用派生而不是使用typedef来表明发生了一些不好的事情.

编辑:

考虑这个用例:

#include <vector>
#include <iostream>

void kill_it(std::vector<double> *victim) { 
    // user code, knows nothing of Rates or Charges

    // invokes non-virtual ~std::vector<double>(), then frees the 
    // memory allocated at address victim
    delete victim ; 

}

typedef std::vector<double> Rates;
class Charges: public std::vector<double> { };

int main(int, char **) {
  std::vector<double> *p1, *p2;
  p1 = new Rates;
  p2 = new Charges;
  // ???  
  kill_it(p2);
  kill_it(p1);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

是否有任何可能的错误,甚至一个任意不幸的用户可以在??? 哪个部分会导致Charges(派生类)出现问题,但不会导致Rate(typedef)?

在Microsoft实现中,vector <T>本身是通过继承实现的.vector <T,A>是公开派生自_Vector_Val <T,A>应该包含首选吗?

rlb*_*ond 25

标准容器没有虚拟析构函数,因此您无法以多态方式处理它们.如果你不这样做,并且每个使用你的代码的人都没有,那么它本身就不是"错误的".但是,为了清晰起见,最好还是使用合成.

  • 在STL容器中使用这种做法时,我发现私有继承是一种在语法上更容易表达组合的方法. (6认同)

Tim*_*imW 16

因为你需要一个虚拟析构函数而std容器没有它.std容器不能用作基类.

有关更多信息,请阅读文章"为什么我们不应该从STL类继承一个类?"

准则

基类必须具有:

  • 公共虚拟析构函数
  • 或受保护的非虚拟析构函数

  • 这适用于设计运行时多态性的情况.如果您不打算将它们视为除了强耦合之外的基本类型,那么从非虚拟类派生没有任何问题. (15认同)
  • @TimW,(1)为派生类添加额外的内存将不会导致泄漏; delete运算符返回最初分配的内存,而不是它所针对的对象的大小.(2)如果某人决定需要额外的实例变量,并且实例变量是PODS,则不会发生泄漏.如果需要一个需要销毁的额外实例变量,那么添加实例变量以跟随继承链以确认析构函数是虚拟的人就有了. (4认同)
  • @Thomas L Holaday:如果这些类是在另一个类的私有部分中声明的,并且显式提示虚拟析构函数的问题那么很好.否则,某个地方的sombody将以一种需要虚拟析构函数的方式使用它们,然后代码将无法调试并找到真正的问题. (2认同)

Nik*_*sov 6

在我看来,一个强有力的反驳是你正在强加一个界面你的类型的实现.当您发现向量内存分配策略不符合您的需求时会发生什么?你会从中衍生出来std:deque吗?那些已经使用你的类的128K代码行怎么样?每个人都需要重新编译一切吗?它会编译吗?

  • 与使用typedef相比,如何使用继承更多的接口和实现? (2认同)

T.E*_*.D. 5

这个问题不是一个哲学问题,而是一个实施问题.标准容器的析构函数不是虚拟的,这意味着无法使用运行时多态性来获取正确的析构函数.

我在实践中发现,使用我的代码需要定义的方法(以及"父"类的私有成员)创建自己的自定义列表类并不是那么痛苦.事实上,它通常会导致设计更好的课程.