使用虚拟方法纠正行为

Correct behavior using virtual methods

本文关键字:方法 虚拟      更新时间:2023-10-16

假设我在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 中实现该方法 - 这正是纯虚函数的含义。但是,您可以添加特殊的失败情况 - 例如返回空列表,或包含包含预定无效值的一个元素的列表。