使用虚拟方法纠正行为

Nic*_*ick 3 c++ polymorphism inheritance virtual-functions

假设我在base接口中有一个纯虚方法,它返回给我一个列表something:

class base 
{ 
public:
     virtual std::list<something> get() = 0; 
}; 
Run Code Online (Sandbox Code Playgroud)

假设我有两个继承base该类的类:

class A : public base 
{ 
public:
     std::list<something> get();
}; 

class B : public base 
{ 
public:
     std::list<something> get(); 
};
Run Code Online (Sandbox Code Playgroud)

我希望只有A类可以返回一个list<something>,但我还需要有可能使用base指针获取列表,例如:

base* base_ptr = new A();
base_ptr->get();
Run Code Online (Sandbox Code Playgroud)

我该怎么办?

我是否要返回指向此列表的指针?参考?

我是否要从类的方法返回空指针B?或者,当我尝试使用B对象获取列表时,是否要抛出异常?或者我要改变base类方法get,使它不纯,并在base课堂上做这个工作?

我还有别的事吗?

Jul*_*bot 9

你别无他法.您提供的代码正是如此.

当您获得指向基类的指针时,由于该方法是在基类中声明的,并且是虚拟的,因此将在类虚函数表中查找实际实现并进行适当调用.

所以

base* base_ptr = new A();
base_ptr->get();
Run Code Online (Sandbox Code Playgroud)

将调用A :: get().你不应该从实现中返回null(你不能,因为null无法转换为std :: list <something>).您必须在A/B中提供实现,因为基类方法被声明为纯虚拟.

编辑:

你不能只返回一个std :: list <something>而不是B,因为B也继承了基类,而基类有一个必须在派生类中重写的纯虚方法.从基类继承是一种"is-a"关系.我能看到的另一种方法是从类中私下继承,但这会阻止派生到基本转换.

如果你真的不希望B有get方法,不要从base继承.一些替代方案是:

在B :: get()中 抛出异常:你可以在B :: get()中抛出一个异常,但要确保你解释你的理由,因为它是违反直觉的.恕我直言这是一个非常糟糕的设计,你可能会混淆使用你的基类的人.这是一个漏洞的抽象,最好避免.

单独的界面: 您可以将基础划分为单独的界面:

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
{

};
Run Code Online (Sandbox Code Playgroud)

在这种情况下,多重继承是可以的,因为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;
    }
};
Run Code Online (Sandbox Code Playgroud)

现在你可以做的是:

base* base_ptr = new A();
IGetSomething *getSmthing = base_ptr->GetSomething();
if (getSmthing != NULL)
{
    std::list<something> listOfSmthing = getSmthing->Get();
}
Run Code Online (Sandbox Code Playgroud)

这是令人费解的,但这种方法有几个优点:

  • 您返回公共接口,而不是具体的实现类.
  • 您可以将继承用于其设计目标.
  • 它很难错误地使用:base不提供std :: list get()因为它不是具体实现之间的常见操作.
  • 您明确了GetSomething()的语义:它允许您返回可用于检索某些内容列表的接口.

    那么只返回一个空的std :: list呢?

这是可能的,但糟糕的设计,就像拥有可以给可口可乐和百事可乐的自动售货机,除了它从未服务百事可乐; 这是误导,最好避免.

那么只返回一个boost :: optional <std :: list <something >>?(正如Andrew所说)

我认为这是一个更好的解决方案,比返回和接口更好,有时可能是NULL,有时不是,因为那时你明确地知道它是可选的,并且没有错误.

缺点是它会在你的界面中加入,我更愿意避免(这取决于我使用boost,但接口的客户端不应该被迫使用boost).