从 C++ STL 容器派生是否有任何真正的风险?

2021-12-17 00:00:00 inheritance c++ stl

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

The claim that it is a mistake ever to use a standard C++ container as a base class surprises me.

如果声明不是滥用语言...

If it is no abuse of the language to declare ...

// Example A
typedef std::vector<double> Rates;
typedef std::vector<double> Charges;

... 那么,声明...的危害究竟是什么

... then what, exactly, is the hazard in declaring ...

// Example B
class Rates : public std::vector<double> { 
    // ...
} ;
class Charges: public std::vector<double> { 
    // ...
} ;

B 的积极优势包括:

  • 启用函数重载,因为 f(Rates &) 和 f(Charges &) 是不同的签名
  • 使其他模板能够被专门化,因为 XX<费用>是不同的类型
  • 前向声明很简单
  • 调试器可能会告诉您对象是 Rates 还是 Charges
  • 如果随着时间的推移,费率和费用会形成个性――费率的单例,费用的输出格式—该功能有明显的实现空间.

A 的积极优势包括:

  • 不必提供构造函数等的琐碎实现
  • 十五年前的标准前编译器是唯一可以编译您的遗留系统的编译器,它不会阻塞
  • 由于不可能进行专业化,因此模板 X和模板X<Charges>将使用相同的代码,因此不会出现毫无意义的膨胀.

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

Both approaches are superior to using a raw container, because if the implementation changes from vector<double> to vector<float>, there's only one place to change with B and maybe only one place to change with A (it could be more, because someone may have put identical typedef statements in multiple places).

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

My aim is that this be a specific, answerable question, not a discussion of better or worse practice. Show the worst thing that can happen as a consequence of deriving from a standard container, that would have been prevented by using a typedef instead.

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

Without question, adding a destructor to class Rates or class Charges would be a risk, because std::vector does not declare its destructor as virtual. There is no destructor in the example, and no need for one. Destroying a Rates or Charges object will invoke the base class destructor. There is no need for polymorphism here, either. The challenge is to show something bad happening as a consequence of using derivation instead of a 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;
}

是否有任何可能的错误,即使是任意不幸的用户也可以在 ???这将导致 Charges(派生类)出现问题,但不会导致 Rates(typedef)出现问题?

Is there any possible error that even an arbitrarily hapless user could introduce in the ??? section which will result in a problem with Charges (the derived class), but not with Rates (the typedef)?

在 Microsoft 实现中,vector本身是通过继承实现的.向量<T,A>是从_Vector_Val<T,A>公开导出的.是否应该首选遏制?

In the Microsoft implementation, vector<T> is itself implemented via inheritance. vector<T,A> is a publicly derived from _Vector_Val<T,A> Should containment be preferred?

推荐答案

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

The standard containers do not have virtual destructors, thus you cannot handle them polymorphically. If you will not, and everyone who uses your code doesn't, it's not "wrong", per se. However, you are better off using composition anyway, for clarity.

相关文章