返回与定义的多态函数不同的类型

时间:2013-01-07 14:42:43

标签: c++ polymorphism

我遇到的问题是多态类:动物类有一个函数“virtual animal * get()= 0;”在dog类中被定义为 “dog * get(){speak2(); return this;}”

我刚才在某处读过这样改变返回类型是合法的,但它看起来并没有像我预期的那样工作:当调用get函数时它打印出预期的值但是当我尝试分配时将值返回给dog类指针我得到了无效的转换错误,当我尝试调用 speak2()函数时,它表示它没有这样的成员。

我正在寻找的是能够按照“barn.front() - > get() - > speak2();”的方式调用某些内容。有没有办法在没有任何dynamic_casts或任何这样的演员阵容的情况下实现与此类似的东西?

我已经以逻辑方式命名了类,因此它易于阅读,并在以下代码中以注释的形式添加了一些注释:

#include <iostream>
#include <vector>
using namespace std;

class animal
{
public:
    virtual ~animal() {}
    virtual void speak1() = 0;
    virtual animal* get() = 0;
};

class dog : public animal
{
public:
    void speak1() { cout << "print-speak1!"; }
    void speak2() { cout << "print-speak2!"; }
    dog* get() { speak2(); return this; }
};

int main()
{
    vector<animal*> barn;

    barn.push_back(new dog());

    barn.front()->speak1();                // prints "print-speak1!"
    barn.front()->get();                   // prints "print-speak2!"

    barn.front()->get()->speak2();
    // error: 'class animal' has no member named 'speak2'
    // but then why does "barn.front()->get();" print "print-speak2!"?

    dog* dogptr = barn.front()->get();
    // error: invalid conversion from 'animal*' to 'dog*' [-fpermissive]

    dogptr->speak2();
    // for the sake of -Werror=unused-variable

    for(vector<animal*>::iterator i = barn.begin(); i != barn.end(); ++i)
    {
        delete *i;
    }
    barn.clear();

    return 0;
}

4 个答案:

答案 0 :(得分:1)

接口编程的重点是使用接口类作为接入点,并依赖于实现的具体类型来符合接口。对你而言,这意味着你应该通过指向动物的指针访问载体中的实际动物。如果将speak2()定义为动物类中的纯虚函数并使用动物*而不是dog *,则程序将运行。如果所有动物都具备speak2()能力,问题仍然存在,但这就是设计问题。请注意,我已将using指令放在类实现下面,因为在库代码中使用namespace指令不是一个好习惯。

以下是带有更改的代码:

#include <iostream>
#include <vector>

class animal
{
public:
    virtual ~animal() {}
    virtual void speak1() = 0;
    virtual void speak2() = 0;
    virtual animal* get() = 0;
};

class dog : public animal
{
public:
    void speak1() { std::cout << "print-speak1!" << std::endl; }
    void speak2() { std::cout << "print-speak2!" << std::endl; }
    dog* get() { speak2(); return this; }
};

using namespace std;

int main()
{
    vector<animal*> barn;

    barn.push_back(new dog());

    barn.front()->speak1();                // prints "print-speak1!"
    barn.front()->get();                   // prints "print-speak2!"

    barn.front()->get()->speak2();
    // error: 'class animal' has no member named 'speak2'
    // but then why does "barn.front()->get();" print "print-speak2!"?

    animal* dogptr = barn.front()->get();
    // error: invalid conversion from 'animal*' to 'dog*' [-fpermissive]

    dogptr->speak2();
    // for the sake of -Werror=unused-variable

    for(vector<animal*>::iterator i = barn.begin(); i != barn.end(); ++i)
    {
        delete *i;
    }
    barn.clear();

    return 0;
}

答案 1 :(得分:1)

您的代码在不同时间发生了两件事。首先,编译器将barn.front()的返回类型视为animal*。无论你做什么。类型speak2()上的呼叫成员animal*始终失败。

您的成员get()是一个虚拟函数,使用虚拟调度调用行barn.front()->get();。这意味着被调用的函数仅在运行时已知基于什么是真实(所谓的动态类型的动物。这样,您就可以修改get()对每只动物的行为。

此处get()的不同返回值无关紧要。编译器执行的类型检查(显然)是在编译时完成的,因此针对animal::get()。协变返回类型仅在您直接调用静态类型dog的对象时才有用。

在你的情况下,一种hackish方式是将类型转换为dog,类似

static_cast<dog*>(barn.front())->speak2();

当然,如果barn.front()的实际类型不是狗,您将刻录。我的意思是未定义的行为 - 程序可能会崩溃,抛出异常或者无声地继续使用损坏的数据。

我的观点中更正确的方法是将不同的行为分成通用界面,例如:

class animal
{
public:
    virtual ~animal() {}
    virtual void makeSound() = 0;
};

class dog : public animal
{
public:
    void bark() { cout << "hoof"; }
    void makeSound() { bark(); }
};

class cat : public animal
{
public:
    void meow() { cout << "meow"; }
    void makeSound() { meow(); }
};

int main()
{
    vector<animal*> barn;
    barn.push_back(new dog());
    barn.push_back(new cat());
    barn.front()->makeSound();
    barn[1]->makeSound();
}

答案 2 :(得分:0)

使用barn->front()->get()时,您可以访问animal*指针。 Speak2()未定义animal,因此no member error

答案 3 :(得分:0)

鉴于

class animal
{
public:
    virtual animal * get () = 0;
};


class dog : animal
{
public:
    virtual dog * get () {...}
};

然后,

// Compiler sees call to "dog::get()".
dog * d = new dog();
d->get();


// Compiler sees call to "animal::get()", which may return
// an instance of any type derived from "animal".
animal * a = d;
d->get();