使用虚拟方法纠正行为
Correct behavior using virtual methods
假设我在base
接口中有一个纯虚拟方法,它向我返回一个something
列表:
class base
{
public:
virtual std::list<something> get() = 0;
};
假设我有两个继承base
类的类:
class A : public base
{
public:
std::list<something> get();
};
class B : public base
{
public:
std::list<something> get();
};
我希望只有 A
类可以返回 list<something>
,但我还需要有可能使用 base
指针获取列表,例如:
base* base_ptr = new A();
base_ptr->get();
我必须做什么?
我是否要返回指向此列表的指针?参考?
我必须从类 B
的方法返回一个空指针吗?还是在尝试使用 B
对象获取列表时引发异常?还是我必须更改base
类方法get
,使其不纯,并在base
类中执行此操作?
我还要做点别的吗?
你没有别的事可做。您提供的代码正是这样做的。
当您获得指向基类的指针时,由于该方法是在基类中声明的,并且是虚拟的,因此将在类虚函数表中查找实际实现并相应地调用。
所以
base* base_ptr = new A();
base_ptr->get();
将调用 A::get()。你不应该从实现中返回 null(好吧你不能,因为 null 不能转换为 std::list<无论如何>)。您必须在 A/B 中提供实现,因为基类方法被声明为纯虚拟。无论如何>
编辑:
你不能只让 A 返回 std::list<>的东西而不是 B,因为 B 也继承基类,并且基类有一个必须在派生类中重写的纯虚拟方法。 从基类继承是一种"is-a"关系。我能看到的唯一另一种方法是私下从类继承,但这会阻止派生到基础的转换。
如果你真的不希望 B 有 get 方法,请不要从 base 继承。一些替代方案是:
在 B::get() 中抛出异常:你可以在 B::get() 中抛出一个异常,但要确保你很好地解释了你的理由,因为它是违反直觉的。恕我直言,这是非常糟糕的设计,您可能会在使用基类时混淆人们。这是一个泄漏的抽象,最好避免。
独立接口:为此,您可以将 base 分解为单独的接口:
class IGetSomething
{
public:
virtual ~IGetSomething() {}
virtual std::list<something> Get() = 0;
};
class base
{
public:
// ...
};
class A : public base, public IGetSomething
{
public:
virtual std::list<something> Get()
{
// Implementation
return std::list<something>();
}
};
class B : public base
{
};
在这种情况下,多重继承是可以的,因为 IGetSomething 是一个纯接口(它没有成员变量或非纯方法)。
编辑2:
根据注释,您似乎希望能够在两个类之间有一个通用接口,但能够执行一个实现执行但另一个实现不提供的某些操作。这是一个相当复杂的场景,但我们可以从COM中汲取灵感(不要向我开枪):
class base
{
public:
virtual ~base() {}
// ... common interface
// TODO: give me a better name
virtual IGetSomething *GetSomething() = 0;
};
class A : public Base
{
public:
virtual IGetSomething *GetSomething()
{
return NULL;
}
};
class B : public Base, public IGetSomething
{
public:
virtual IGetSomething *GetSomething()
{
// Derived-to-base conversion OK
return this;
}
};
现在你可以做的是:
base* base_ptr = new A();
IGetSomething *getSmthing = base_ptr->GetSomething();
if (getSmthing != NULL)
{
std::list<something> listOfSmthing = getSmthing->Get();
}
它很复杂,但这种方法有几个优点:
返回公共接口,而不是具体的实现类。
您将继承用于其设计目的。
很难错误地使用:base 不提供 std::list get(),因为它不是具体实现之间的常见操作。
你对 GetSomething() 的语义是明确的:它允许你返回一个可用于检索某物列表的接口。
只返回一个空的 std::list 怎么样?
这是可能的,但设计很糟糕,就像有一台自动售货机可以提供可口可乐和百事可乐,但它从不为百事可乐服务;它具有误导性,最好避免。
如果只是返回一个提升::可选<std::list><>>的东西呢?(根据安德鲁的建议)
我认为这是一个更好的解决方案,比返回和接口更好,有时可能是空的,有时不是,因为这样你就会明确知道它是可选的,并且不会有错误。
缺点是它会在你的界面中放置 boost,我宁愿避免这种情况(由我决定使用 boost,但接口的客户端不应该被迫使用 boost)。
返回boost::optional
,以防您需要不返回(在 B 类中)
class base
{
public:
virtual boost::optional<std::list<something> > get() = 0;
};
你做的是错的。如果它对两个派生类都不通用,则可能不应将其放在基类中。
除此之外,没有办法实现你想要的。你还必须在 B 中实现该方法 - 这正是纯虚函数的含义。但是,您可以添加特殊的失败情况 - 例如返回空列表,或包含包含预定无效值的一个元素的列表。
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 跨 DLL 边界访问虚拟方法是否安全/可能?
- 是否可以使用基类非虚拟方法中的派生类虚拟方法?
- 如何编写 operator= 用于使用虚拟方法与非平凡成员的匿名联合
- 让编译器告诉什么确切的纯虚拟方法使结构抽象?
- 使用模板而不是虚拟方法的管道模式
- 派生类调用父类的方法,该方法调用重写的虚拟方法调用错误的方法
- 从纯虚拟类 (A) 派生的指针无法访问来自纯类 (B) 的重载方法
- 为什么调用没有正文的纯虚拟方法不会导致链接器错误?
- 出于什么目的,非虚拟方法将与C++一起使用?
- 为什么使用存储在虚拟方法表中的地址调用虚拟函数的函数会返回垃圾?
- 如何重写继承的嵌套类中存在的虚拟方法
- 私有虚拟方法有什么用?
- 派生类中纯虚拟基方法的专业化
- 基类可以声明虚拟方法但不定义它吗?仍然在派生类中定义
- googletest:测试基类具有纯虚拟方法的派生类时的核心转储
- 确保模拟的 GTest 方法覆盖虚拟方法
- 使用回调函数从构造函数调用虚拟/派生方法的替代方法?
- 如何调用孩子的方法:虚拟关键字不起作用