C++抽象基类的函数
假设我想要有一个像这样的继承层次结构。
class Base
class DerivedOne : public Base
class DerivedTwo : public Base
基类不打算被实例化,因此具有派生类必须定义的一些纯虚函数,使其成为抽象基类。
但是,您希望派生类从基类获取一些函数。这些函数修改 DerivedOne 和 DerivedTwo 都具有的私有数据成员。
class Base {
public:
virtual void MustDefine() =0; // Function that derived classes must define
void UseThis(); // Function that derived classes are meant to use
};
但是,UseThis()
函数旨在修改私有数据成员。这就是问题所在。我应该为 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 DerivedOne 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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
如果这些成员变量应该存在于所有派生类中,那么您应该在基类中声明它们。如果您担心封装,可以将它们设置为私有并为派生类提供受保护的访问器方法。
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 provideprotected
accessor methods for derived classes.另外五美分:好的做法是拥有抽象接口类,它没有其他成员,但只有公共纯虚拟方法和通常公共虚拟析构函数。然后,您创建基本实现,它也可以是抽象的,但可以具有受保护的字段等。
在您的情况下,它会是这样的:
如果您决定 Base 不再适合某些专门任务,这可以让您在将来拥有更大的灵活性。
如果您可以实现部分功能而不将详细信息暴露给派生类,那么可以在基类中实现。如果您的派生类需要访问这些成员,请提供 setter 和 getter。但是,为派生类提供可用的 setter 并不方便,因为代码变得紧密耦合。
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:
This allows you to have more flexibility in the future if you decide that Base is no longer good for some specialized task.
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.
封装有时被高估了。如果您的基类和派生类需要访问这些成员,那么它们可能应该是
protected
,而不是private
。如果它确实是需要封装的东西,那么您可能希望将它们设置为私有
,但提供getter和setter(要么将它们设置为Base
私有,并使用getter和setter在那里定义,或者派生类私有,在Base
中使用纯虚拟getter和setter)。在不了解您要解决的实际问题的情况下,很难为您提供更具体的建议。
Encapsulation is sometimes overrated. If your base class and derived classes need to access those members, then they should probably be
protected
, notprivate
. If it really is something that needs to be encapsulated, then you may want to make themprivate
but provide getters and setters (either make them private toBase
, with getters and setters defined there, or private to the derived classes, with pure virtual getters and setters inBase
).It's a bit hard to give you more specific advice without knowing about the actual problem you're trying to solve.
您必须定义 Base::UseThis(),在其主体中您将使用 Base 的字段(您还需要在上面的类定义中声明这些字段)。如果您只需要在 UseThis 中访问它们,它们可以是私有的。如果 DerivedOne/Two 需要访问它们,您应该保护它们。
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:
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.
看起来您似乎需要一些用于客户端代码的接口,以及一些用于接口实现者的“方便”功能,只有遵循调用便利层的 useThis 函数的规则,他们才能使用这些功能,这将调整他们的私人成员。
每当我屈服于将便利功能放入抽象基类的诱惑时,我就会后悔(很快!)。它剥夺了很多灵活性。 AlexKR提出的解决方案使这种情况稍微好一些。
另一种方法是提供一些方便的类,接口的实现者可以聚合这些类,而不是继承它。它可以提供一个将实现者的成员作为参数的函数。
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.