声称使用标准C ++容器作为基类是错误的说法让我感到惊讶。
如果没有滥用语言宣布......
// Example A
typedef std::vector<double> Rates;
typedef std::vector<double> Charges;
......那么确切地说,宣告中的危险是什么......
// Example B
class Rates : public std::vector<double> {
// ...
} ;
class Charges: public std::vector<double> {
// ...
} ;
B的积极优势包括:
A的积极优势包括:
这两种方法都优于使用原始容器,因为如果实现从vector&lt; double&gt;更改to vector&lt; float&gt;,只有一个地方可以改变B和也许只有一个地方可以改变A(可能更多,因为有人可能在多个地方放置了相同的typedef语句)。< / p>
我的目标是,这是一个具体的,可回答的问题,而不是对更好或更差实践的讨论。显示由于从标准容器派生而可能发生的最糟糕的事情,这可以通过使用typedef来防止。
编辑:
毫无疑问,向类Rate或类Charges添加析构函数会有风险,因为std :: vector不会将其析构函数声明为virtual。示例中没有析构函数,也不需要析构函数。销毁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;
}
是否有任何可能的错误,甚至一个任意不幸的用户可能会在???哪个部分会导致Charges(派生类)出现问题,但不会导致Rate(typedef)?
在Microsoft实现中,向量&lt; T&gt;本身是通过继承实现的。矢量&lt; T,A&GT;是公开衍生自_Vector_Val&lt; T,A&gt;应该遏制是首选吗?
答案 0 :(得分:25)
标准容器没有虚拟析构函数,因此您无法以多态方式处理它们。如果你不这样做,并且每个使用你代码的人都不这样做,那本身就不是“错误的”。但是,为了清晰起见,最好还是使用合成。
答案 1 :(得分:16)
因为你需要一个虚拟析构函数而std容器没有它。 std容器不能用作基类。
有关详情,请参阅文章"Why shouldn't we inherit a class from STL classes?"
<强>准则强>
基类必须具有:
答案 2 :(得分:6)
在我看来,一个强有力的反驳是你在你的类型上强加了一个接口和。当您发现向量内存分配策略不符合您的需求时会发生什么?你会从std:deque
派生出来吗?那些已经使用你的类的128K代码行怎么样?每个人都需要重新编译一切吗?它会编译吗?
答案 3 :(得分:5)
这个问题不是一个哲学问题,而是一个实施问题。标准容器的析构函数不是虚拟的,这意味着无法使用运行时多态性来获取正确的析构函数。
我在实践中发现,使用我的代码需要定义的方法(以及“父”类的私有成员)创建我自己的自定义列表类并不是那么痛苦。事实上,它通常会带来设计更好的课程。
答案 4 :(得分:3)
此外,在大多数情况下,如果可能,您应该优先选择合成或聚合而不是继承。
答案 5 :(得分:3)
除了基类需要虚拟析构函数或受保护的非虚拟析构函数之外,您还在设计中进行以下断言:
费率和费用,与上面示例中的双打矢量相同。根据你自己的断言“...随着时间的推移,费率和收费会产生个性......”然后是说明利率仍然是相同的此时双倍的载体?例如,双向量不是单例,因此如果我使用你的比率来声明我的小部件的双向量,我可能会从你的代码中引起一些麻烦。关于费率和收费还有哪些变化?如果它们的基本方式有所改变,那么任何基类更改是否可以安全地与您设计的客户保持隔离?
关键是一个类是C ++中许多用来表达设计意图的元素。说出你的意思和意思是你说的是反对以这种方式使用继承的原因。
...或者只是在我的回答之前更简洁地发布:替换。
答案 6 :(得分:1)
一个字:Substitutability
答案 7 :(得分:1)
是否有任何可能的错误,甚至一个任意不幸的用户可能会在???哪个部分会导致Charges(派生类)出现问题,但不会导致Rate(typedef)?
首先,Mankarse的优点是:
kill_it
中的评论错误。如果受害者的动态类型不是std::vector
,那么delete
只会调用未定义的行为。对kill_it(p2)
的调用会导致这种情况发生,因此不需要向//???
部分添加任何内容以使其具有未定义的行为。 - Mankarse Sep 3 '11 at 10:53
其次,说他们致电f(*p1);
f
专门针对std::vector<double>
:vector
专业化将无法找到 - 您最终可能会以不同的方式匹配模板专精化 - 通常运行(速度较慢或效率较低)通用代码,或者如果未实际定义非专用版本则会出现链接器错误。 通常不是一个重要问题。
就我个人而言,我认为通过一个指向base的指针进行破坏是为了跨越这条线 - 鉴于你当前的编译器,编译器标志,可能只是一个“假设的”问题(据你所知) ,程序,操作系统版本等 - 但它可以随时打破,没有“好”的理由。
如果您确信可以通过基类指针避免删除,请选择它。
那就是说,关于你的评估的一些注释:
template <typename A> Classname(const A& a) : Base(a) { } template <typename A, typename B> Classname(const A& a, const B& b) : Base(a, b) { } ...
有时比枚举所有重载更容易,但不处理非 - {{1}参数,默认值,显式vs非显式构造函数,也不扩展到大量参数。 C ++ 11提供了更好的通用解决方案。毫无疑问,向
const
或class Rates
添加析构函数会带来风险,因为class Charges
并未将其析构函数声明为虚拟。示例中没有析构函数,也不需要析构函数。销毁Rates或Charges对象将调用基类析构函数。这里也不需要多态性。
如果对象未被多态删除,则派生类析构函数不会带来风险;如果它是未定义的行为,无论您的派生类是否具有用户定义的析构函数。也就是说,当您使用执行清理的析构函数添加数据成员或其他基础时,您可以从“可能为牛仔”转为“几乎肯定不是正常”(内存释放,互斥锁解锁,文件)处理结束等。)
说“将调用基类析构函数”使得它听起来像直接完成,没有涉及隐式定义的派生类析构函数或进行调用 - 所有优化细节都没有由标准指定。