简体   繁体   English

抽象基类的C ++函数

[英]C++ Functions for an Abstract Base Class

Suppose I want to have an inheritance hierarchy like this. 假设我想拥有这样的继承层次结构。

class Base

class DerivedOne : public Base

class DerivedTwo : public Base

The base class is not meant to be instantiated, and thus has some pure virtual functions that the derived classes must define, making it an abstract base class. 基类不是要实例化的,因此有一些派生类必须定义的纯虚函数,使它成为一个抽象基类。

However, there are some functions that you would like your derived classes to get from your base class. 但是,您希望派生类从基类中获取一些函数。 These functions modify private data members that both DerivedOne and DerivedTwo will have. 这些函数修改DerivedOneDerivedTwo都将拥有的私有数据成员。

class Base {
public:
      virtual void MustDefine() =0; // Function that derived classes must define
      void UseThis(); // Function that derived classes are meant to use
};

However, the UseThis() function is meant to modify private data members. 但是, UseThis()函数用于修改私有数据成员。 That's where the question comes in. Should I give the Base class dummy private data members? 这就是问题所在。我应该给Base类虚拟私有数据成员吗? Should I give it protected data members (and thus the derived classes won't declare their own private data members). 我应该给它受保护的数据成员(因此派生类不会声明自己的私有数据成员)。 I know the second approach will decrease encapsulation. 我知道第二种方法会减少封装。

What is the best approach to a situation like this? 这种情况的最佳方法是什么? If a more detailed explanation is needed I'd be happy to provide it. 如果需要更详细的解释,我很乐意提供。

If those member variables are supposed to exist in all derived classes then you should declare them in the base class. 如果这些成员变量应该存在于所有派生类中,那么您应该在基类中声明它们。 If you are worried about encapsulation, you can make them private and provide protected accessor methods for derived classes. 如果您担心封装,可以将它们private并为派生类提供protected访问器方法。

Another five cents: the good practice is to have abstract interface class which has no other members, but only public pure virtual methods and often public virtual destructor. 另外五美分:好的做法是拥有抽象的接口类,它没有其他成员,只有公共的纯虚方法和公共虚拟析构函数。 Then you create base implementation which can also be abstract but can have protected fields, etc. 然后你创建基本实现,它也可以是抽象的,但可以有受保护的字段等。

In you case it would be something like: 在你的情况下,它将是这样的:

class IBlaBla;

class BlaBlaBase : public IBlaBla;

class DerivedOne : public BlaBlaBase

class DerivedTwo : public BlaBlaBase

This allows you to have more flexibility in the future if you decide that Base is no longer good for some specialized task. 如果您认为Base不再适合某些特定任务,那么这将使您在将来拥有更大的灵活性。

Should I give the Base class dummy private data members? 我应该给Base类虚拟私有数据成员吗?

If you can implement a part of functionality without exposing the details to the derived classes, then do it in base class. 如果您可以实现一部分功能而不将细节暴露给派生类,那么在基类中执行它。 If your derived classes would need access to these members, provide setters and getters. 如果派生类需要访问这些成员,请提供setter和getter。 However, it is not convenient to have setters available for derived classes because your code becomes tightly coupled. 但是,为派生类设置setter是不方便的,因为代码紧密耦合。

Encapsulation is sometimes overrated. 封装有时被高估了。 If your base class and derived classes need to access those members, then they should probably be protected , not private . 如果您的基类和派生类需要访问这些成员,那么它们应该受到protected ,而不是private If it really is something that needs to be encapsulated, then you may want to make them private but provide getters and setters (either make them private to Base , with getters and setters defined there, or private to the derived classes, with pure virtual getters and setters in Base ). 如果它确实是需要封装的东西,那么你可能想要将它们设为private但是提供getter和setter(要么将它们作为私有对Base ,在那里定义getter和setter,要么对派生类是私有的,使用纯虚拟getter和Base )。

It's a bit hard to give you more specific advice without knowing about the actual problem you're trying to solve. 在不知道您要解决的实际问题的情况下,向您提供更具体的建议有点困难。

You will have to define Base::UseThis(), in the body of which you will make use of Base's fields (which you will also need to declare in the class definition above). 您将必须定义Base :: UseThis(),在其主体中您将使用Base的字段(您还需要在上面的类定义中声明)。 If you only need to access them in UseThis, they can be private. 如果您只需要在UseThis中访问它们,它们可以是私有的。 If DerivedOne/Two will need access to them, you should make them protected. 如果DerivedOne / Two需要访问它们,您应该保护它们。

Here is a possible resolution to your dilemna: 这是你的困境的可能解决方案:

class Base {
 public:
   virtual ~Base() {}

   virtual void redefine_me() = 0;
   void utility_function();

 private:
   virtual int get_data_member() = 0;
   virtual void set_data_member(int v) = 0;
};

class Derived1 : public Base {
 public:
   virtual void redefine_me() { do_d1_stuff(); }

 private:
   int my_private_idaho_;
   virtual int get_data_member() { return my_private_idaho_; }
   virtual void set_data_member(int v) { my_rpviate_idaho_ = v; }
};

class Derived2 : public Base {
 public:
   virtual void redefine_me() { do_d2_stuff(); }

 private:
   int gomer_pyle_;
   virtual int get_data_member() { return gomer_pyle_; }
   virtual void set_data_member(int v) { gomer_pyle_ = v; }
};

void Base::utility_function()
{
   set_data_member(get_data_member() + 1);
}

It's biggest disadvantage is that now access to the private data member is mediated by a virtual function call, which isn't the cheapest thing around. 它最大的缺点是现在访问私有数据成员是由虚函数调用调解的,这不是最便宜的事情。 It's also hidden from the optimizer. 它也被优化器隐藏了。

This means that if you choose it, you should adopt a pattern where you fetch the private data member into a local variable at the beginning of your utility function and set it from the local variable before you return. 这意味着如果您选择它,则应该采用一种模式,在实用程序函数开头将私有数据成员提取到局部变量中,并在返回之前从局部变量设置它。 Of course some utility functions may call out to functions that require the object state to be updated before they're called, and this pattern would then have to be modified to account for that. 当然,一些实用程序函数可能会调用需要在调用对象状态之前更新的函数,然后必须修改此模式以解决该问题。 But then again, such utility functions are likely not to be able to satisfy the strong exception handling guarantee and should be rethought anyway. 但话说回来,这样的效用函数很可能无法满足强大的异常处理保证,无论如何都应该重新考虑。

It looks as if you need some interface for client code, and some 'convenient' functionality for implementors of the interface, which they can only use if they follow the rule of calling the useThis function of the convenience layer, which will tweak their private members. 它看起来好像你需要一些客户端代码接口,以及接口实现者的一些“方便”功能,只有当他们遵循调用便利层的useThis函数的规则时才能使用,这将调整他们的私有成员。

Whenever I gave in to the temptation of putting the convenience functionality in my abstract base class, I regretted it (soon!) afterwards. 每当我屈服于将便利功能放在我的抽象基类中的诱惑时,我很后悔(很快!)。 It takes away a lot of flexibility. 它带走了很多灵活性。 The solution proposed by AlexKR makes this situation slightly better. AlexKR提出解决方案使这种情况稍好一些。

An alternative way of doing this is providing some convenience class that implementers of the interface can aggregate instead of inheriting it. 另一种方法是提供一些便利类,接口的实现者可以聚合而不是继承它。 It can provide a function taking the implementer's members as arguments . 它可以提供一个函数,将实现者的成员作为参数

class Interface { public: virtual void f() = 0; };

class Convenience { 
   public: 
   void tweakMyMembers( int& member1, float& member2 ); 
   bool somestate;
};

class Implementor : public Interface {
   int m1; float m2;
   public: Implementor( bool b ): conv( b ) {}

   virtual void f() { conv.tweakMyMembers( m1, m2 ); if( m1<m2 ) dothis(); }
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM