iam*_*ind 48 c++ inheritance standard-library
我经常在Stack Overflow上阅读这些语句.就个人而言,我没有发现任何问题,除非我以多态方式使用它; 即我必须使用virtual析构函数.
如果我想扩展/添加标准容器的功能那么什么是比继承一个更好的方法?将这些容器包装在自定义类中需要更多的努力并且仍然是不洁净的.
ex0*_*du5 82
这有一个坏主意有很多原因.
首先,这是一个坏主意,因为标准容器没有虚拟析构函数.你不应该使用没有虚拟析构函数的多态的东西,因为你无法保证派生类的清理.
其次,这是非常糟糕的设计.实际上有几个原因是糟糕的设计.首先,您应该始终通过一般操作的算法扩展标准容器的功能.这是一个简单的复杂性原因 - 如果你必须为它应用的每个容器编写一个算法,你有M个容器和N个算法,那就是你必须编写的M x N方法.如果您通常编写算法,则只能使用N算法.所以你可以获得更多的重用.
它也是非常糟糕的设计,因为你通过继承容器来打破良好的封装.一个好的经验法则是:如果您可以使用类型的公共接口执行所需的操作,请将该新行为置于该类型的外部.这改善了封装.如果它是您想要实现的新行为,请将其设置为命名空间作用域函数(如算法).如果要强制使用新的不变量,请在类中使用包含.
最后,一般来说,您永远不应该将继承视为扩展类行为的手段.这是由于对重用的思考不清晰而引起的早期OOP理论的重大坏处之一,即使有一个明确的理论为什么它是坏的,它仍然被教导和推广到今天.当您使用继承来扩展行为时,您将这种扩展行为绑定到您的接口契约,以便将用户的手与未来的更改联系起来.例如,假设您有一个类型为Socket的类,它使用TCP协议进行通信,并通过从Socket派生类SSLSocket并在Socket之上实现更高SSL堆栈协议的行为来扩展它的行为.现在,假设你有一个新的要求,即通过USB线或通过电话获得相同的通信协议.您需要将所有工作剪切并粘贴到从USB类或Telephony类派生的新类中.而现在,如果你发现了一个bug,你必须在所有三个地方修复它,这并不总是会发生,这意味着错误会花费更长的时间并且不会总是得到修复......
这对于任何继承层次结构都是通用的 - > B-> C - > ...当你想要使用你在派生类中扩展的行为时,比如B,C,..对非基类A的对象,你必须重新设计或者你正在重复实施.这导致非常单一的设计很难改变(想想微软的MFC,或他们的.NET,或者 - 好吧,他们犯了很多错误).相反,你应该尽可能地考虑通过构图进行扩展.在考虑"开放/封闭原则"时应该使用继承.您应该通过继承类具有抽象基类和动态多态运行时,每个都将完全实现.层次结构不应该很深 - 几乎总是两个层次.当您拥有不同的动态类别时,只能使用两个以上的动态类别,这些类别需要区分类型安全性的各种功能.在这些情况下,使用抽象基础直到叶类,它们具有实现.
Emi*_*lia 27
可能很多人在这里不会喜欢这个答案,但现在是时候让一些异端被告知,是的......也被告知"国王是赤身裸体的!"
反对推导的所有动机都很弱.推导与构成没有什么不同.这只是"把事情放在一起"的一种方式.组合将事物放在一起给它们命名,继承是在不给出明确名称的情况下完成的.
如果你需要一个具有相同接口和std :: vect实现的向量以及更多东西,你可以:
使用组合并重写所有嵌入式对象函数原型实现委托它们的函数(如果它们是10000 ...是:准备重写所有10000)或者......
继承它并添加你需要的东西(并且...只重写构造函数,直到C++律师将决定让它们也可以继承:我还记得10年前狂热者讨论"为什么ctors不能互相称呼"以及为什么呢是一个"坏的坏事"......直到C++ 11允许它并突然所有那些狂热者闭嘴!)并且让新的析构函数不是虚拟的,因为它是原始的.
就像每个有一些虚拟方法的类而有些没有,你知道你不能假装通过寻址基来调用派生的非虚方法,同样适用于删除.删除没有理由假装任何特殊的护理.一个程序员知道什么不是虚拟的不可调用基地也知道在分配你的派生后你不会在你的基础上使用删除.
所有"避免这种情况""不要那样做"总是听起来像是一种本能不可知的"道德化".存在语言的所有特征以解决某些问题.解决问题的一种方法是好还是坏取决于上下文,而不是取决于特征本身.如果您正在做的事情需要为许多容器提供服务,那么继承可能不是那种方式(您必须为所有人重做).如果是针对特定情况......继承是一种撰写方式.忘记OOP纯粹主义:C++不是"纯OOP",容器根本不是OOP.
你应该避免公开从标准的contianers.您可以选择私有继承和组合,在我看来,所有通用指南都表明组合在这里更好,因为您不会覆盖任何功能.不要公开形成STL容器 - 实际上并不需要它.
顺便说一句,如果你想在容器中添加一堆算法,可以考虑将它们添加为采用迭代器范围的独立函数.
由于其他人陈述的所有原因,公开继承是一个问题,即,您的容器可以上载到没有虚拟析构函数或虚拟赋值运算符的基类,这可能导致切片问题。
另一方面,私有继承问题不大。考虑以下示例:
#include <vector>
#include <iostream>
// private inheritance, nobody else knows about the inheritance, so nobody is upcasting my
// container to a std::vector
template <class T> class MyVector : private std::vector<T>
{
private:
// in case I changed to boost or something later, I don't have to update everything below
typedef std::vector<T> base_vector;
public:
typedef typename base_vector::size_type size_type;
typedef typename base_vector::iterator iterator;
typedef typename base_vector::const_iterator const_iterator;
using base_vector::operator[];
using base_vector::begin;
using base_vector::clear;
using base_vector::end;
using base_vector::erase;
using base_vector::push_back;
using base_vector::reserve;
using base_vector::resize;
using base_vector::size;
// custom extension
void reverse()
{
std::reverse(this->begin(), this->end());
}
void print_to_console()
{
for (auto it = this->begin(); it != this->end(); ++it)
{
std::cout << *it << '\n';
}
}
};
int main(int argc, char** argv)
{
MyVector<int> intArray;
intArray.resize(10);
for (int i = 0; i < 10; ++i)
{
intArray[i] = i + 1;
}
intArray.print_to_console();
intArray.reverse();
intArray.print_to_console();
for (auto it = intArray.begin(); it != intArray.end();)
{
it = intArray.erase(it);
}
intArray.print_to_console();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
输出:
1
2
3
4
5
6
7
8
9
10
10
9
8
7
6
5
4
3
2
1
Run Code Online (Sandbox Code Playgroud)
干净简单,让您无需花费太多精力即可自由扩展std容器。
如果您考虑做一些愚蠢的事情,例如:
std::vector<int>* stdVector = &intArray;
Run Code Online (Sandbox Code Playgroud)
你得到这个:
error C2243: 'type cast': conversion from 'MyVector<int> *' to 'std::vector<T,std::allocator<_Ty>> *' exists, but is inaccessible
Run Code Online (Sandbox Code Playgroud)
问题是您或其他人可能会意外地将扩展类传递给需要引用基类的函数。这将有效地(并且默默地!)切断扩展并产生一些难以发现的错误。
相比之下,编写一些转发函数似乎是一个很小的代价。
| 归档时间: |
|
| 查看次数: |
16972 次 |
| 最近记录: |