模板还是抽象基类?
如果我想使类具有适应性,并且可以从外部select不同的algorithm – 在C ++中最好的实现是什么?
我主要看到两种可能性:
- 使用抽象基类并传入具体对象
- 使用模板
这里有一个小例子,在各个版本中实现:
版本1:抽象基类
class Brake { public: virtual void stopCar() = 0; }; class BrakeWithABS : public Brake { public: void stopCar() { ... } }; class Car { Brake* _brake; public: Car(Brake* brake) : _brake(brake) { brake->stopCar(); } };
版本2a:模板
template<class Brake> class Car { Brake brake; public: Car(){ brake.stopCar(); } };
版本2b:模板和私有inheritance
template<class Brake> class Car : private Brake { using Brake::stopCar; public: Car(){ stopCar(); } };
来自Java,我自然倾向于总是使用版本1,但模板版本似乎经常是首选,例如在STL代码? 如果这是真的,是因为内存效率等(没有inheritance,没有虚函数调用)?
我意识到版本2a和2b之间没有太大差别,请参阅C ++ FAQ 。
你能评论这些可能性吗?
这取决于你的目标。 如果你可以使用版本1
- 打算replace一辆车的刹车(在运行时)
- 打算将Car转到非模板function
我通常更喜欢版本1使用运行时多态性,因为它仍然是灵活的,并允许你有汽车仍然有相同的types: Car<Opel>
是另一种types比Car<Nissan>
。 如果您的目标在经常使用刹车时performance出色,我build议您使用模板化方法。 顺便说一下,这就是所谓的基于策略的devise。 你提供一个刹车政策 。 例子,因为你说你用Java编程,可能你还没有太熟悉C ++。 一种做法:
template<typename Accelerator, typename Brakes> class Car { Accelerator accelerator; Brakes brakes; public: void brake() { brakes.brake(); } }
如果你有很多的策略,你可以将它们组合在一起,然后通过它们,例如作为SpeedConfiguration
收集Accelerator
, Brakes
等等。 在我的项目中,我尝试保留大量的无模板代码,允许它们一次编译到自己的对象文件中,而不需要头文件中的代码,但仍然允许多态(通过虚拟函数)。 例如,您可能希望保留非模板代码可能会在基类中多次调用的通用数据和函数:
class VehicleBase { protected: std::string model; std::string manufacturer; // ... public: ~VehicleBase() { } virtual bool checkHealth() = 0; }; template<typename Accelerator, typename Breaks> class Car : public VehicleBase { Accelerator accelerator; Breaks breaks; // ... virtual bool checkHealth() { ... } };
顺便说一句,这也是C ++stream的使用方法: std::ios_base
包含标志和不依赖于chartypes或类似openmode,格式标志和东西等特性的东西,而std::basic_ios
则是inheritance的类模板它。 这也通过共享类模板的所有实例的共同代码来减less代码膨胀。
私人inheritance?
一般应避免私人遗产。 在大多数情况下,这只是非常有用的,遏制是一个更好的主意。 当大小非常重要时(例如基于策略的string类),情况正好相反。空基类优化可以在从空策略类(仅包含函数)派生时应用。
阅读草药的使用和滥用inheritance 。
经验法则是:
1)如果在编译时select具体types,则更喜欢模板。 这会更安全(编译时间错误与运行时间错误),并可能更好地优化。 2)如果select是在运行时进行的(即由于用户的行为),实际上没有select – 使用inheritance和虚函数。
其他选项:
- 使用访问者模式 (让外部代码在你的课堂上工作)。
- 外部化你的类的某些部分,例如通过迭代器,通用的基于迭代器的代码可以处理它们。 如果你的对象是其他对象的容器,这个效果最好。
- 另请参阅策略模式 (里面有c ++的例子)
模板是让一个类使用一个你不关心types的variables的一种方法。 inheritance是一种根据属性定义类的方法。 它的“是 – 是”与“有”是一个问题。
你的问题大部分已经被回答了,但是我想详细说明这一点:
来自Java,我自然倾向于总是使用版本1,但模板版本似乎经常是首选,例如在STL代码? 如果这是真的,是因为内存效率等(没有inheritance,没有虚函数调用)?
这是它的一部分。 但另一个因素是增加型的安全性。 当您将BrakeWithABS
视为Brake
,会丢失types信息。 你不再知道这个对象实际上是一个BrakeWithABS
。 如果它是一个模板参数,则具有可用的确切types,在某些情况下可以使编译器执行更好的types检查。 或者它可能有助于确保函数的正确重载被调用。 (如果stopCar()
将Brake对象传递给第二个函数,该函数可能对BrakeWithABS
有单独的重载,如果使用inheritance,则BrakeWithABS
不会被调用,而BrakeWithABS
已经被转换为Brake
。
另一个因素是它允许更多的灵活性。 为什么所有的Brake实现都必须从同一个基类inheritance? 基类实际上是否有什么可以带来的表格? 如果我写一个暴露预期成员职能的课,是不是可以起到刹车的作用呢? 通常,显式使用接口或抽象基类会限制您的代码。
(注意,我并不是说模板应该始终是首选的解决scheme,还有其他一些可能会影响到编译速度的问题,从编译速度到“我的团队中的程序员都熟悉”,还是仅仅是“我喜欢的”。 ,您需要运行时多态性,在这种情况下,模板解决scheme根本无法实现)
这个答案或多或less是正确的。 当你想在编译时参数化的时候 – 你应该更喜欢模板。 当你想在运行时参数化的时候,你应该更喜欢被覆盖的虚拟函数。
但是 ,使用模板并不妨碍您同时执行这两个操作(使模板版本更加灵活):
struct Brake { virtual void stopCar() = 0; }; struct BrakeChooser { BrakeChooser(Brake *brake) : brake(brake) {} void stopCar() { brake->stopCar(); } Brake *brake; }; template<class Brake> struct Car { Car(Brake brake = Brake()) : brake(brake) {} void slamTheBrakePedal() { brake.stopCar(); } Brake brake; }; // instantiation Car<BrakeChooser> car(BrakeChooser(new AntiLockBrakes()));
这就是说,我可能不会使用这个模板…但它真的只是个人的品味。
抽象基类具有虚拟调用的开销,但它有一个优点,即所有派生类都是基类。 当你使用模板的时候不是这样的 – Car <Brake>和Car <BrakeWithABS>是互不相关的,你必须要么dynamic_cast,要么检查null,要么有与Car有关的所有代码的模板。
如果您希望一次支持不同的Break类及其层次结构,请使用接口。
Car( new Brake() ) Car( new BrakeABC() ) Car( new CoolBrake() )
而且在编译时你不知道这些信息。
如果你知道你打算使用哪个Break 2b是你指定不同Car类的正确select。 在这种情况下刹车将是你的车“策略”,你可以设置默认的。
我不会用2a。 相反,您可以将静态方法添加到Break并在没有实例的情况下调用它
就个人而言,我总是喜欢通过模板使用接口,原因有几个:
- 编译和链接错误有时是模糊的
- 很难debugging基于模板的代码(至less在Visual Studio IDE中)
- 模板可以使你的二进制文件更大。
- 模板要求你把所有的代码放在头文件中,这使得模板类有点难以理解。
- 新手程序员难以维护模板。
我只在虚拟表创build某种开销时使用模板。
当然,这只是我的自我意见。