开发者

C++ Functions for an Abstract Base Class

开发者 https://www.devze.com 2022-12-09 19:55 出处:网络
Suppose I want to have an inheritance hierarchy like this. class Base class DerivedOne : public Base class DerivedTwo : public Base

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 Der开发者_开发技巧ivedOne and DerivedTwo will have.

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. That's where the question comes in. Should I give the Base class dummy private data members? 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.


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.

Should I give the Base class dummy private data members?

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. However, it is not convenient to have setters available for derived classes because your code becomes tightly coupled.


Encapsulation is sometimes overrated. If your base class and derived classes need to access those members, then they should probably be protected, not 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).

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). If you only need to access them in UseThis, they can be private. If DerivedOne/Two will need access to them, you should make them protected.


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.

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.

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(); }
};
0

精彩评论

暂无评论...
验证码 换一张
取 消