使用虚拟方法纠正行为

时间:2012-07-06 08:08:34

标签: c++ inheritance polymorphism virtual-functions

假设我在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类中执行此操作?

我还有其他事吗?

3 个答案:

答案 0 :(得分:9)

你没有别的事可做。您提供的代码正是如此。

当你得到一个指向基类的指针时,由于该方法是在基类中声明的,并且是虚拟的,所以实际的实现将在类虚函数表中查找并适当调用。

所以

base* base_ptr = new A();
base_ptr->get();

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

修改

你不能只有A返回一个std :: list&lt;的东西&gt;而不是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
{

};

在这种情况下的多重继承是正常的,因为IGetSomething是一个纯接口(它没有成员变量或非纯方法)。

<强> EDIT2

根据评论,您似乎希望能够在两个类之间具有公共接口,但能够执行一个实现所执行的操作,但另一个不提供。这是一个非常令人费解的场景,但我们可以从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?

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

  

返回一个boost :: optional&lt;的std ::列表&LT;的东西&gt; &GT; ? (正如Andrew所说)

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

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

答案 1 :(得分:1)

如果您需要能够不返回(在B类中)

,请返回boost::optional
class base 
{ 
public:
     virtual boost::optional<std::list<something> > get() = 0; 
}; 

答案 2 :(得分:1)

你在做什么是错的。如果两个派生类都不常见,那么你可能不应该在基类中使用它。

除此之外,没有办法实现你想要的。您还必须在B中实现该方法 - 这正是纯虚函数的含义。但是,您可以添加特殊的失败案例 - 例如返回空列表,或者包含一个包含预定无效值的元素的列表。