7

参考代码:

#include <vector>
#include <iostream>

class Func {
public:
    virtual void call() {
        std::cout<< "Func -> call()" << std::endl;
    }
};

class Foo : public Func {
public:
    void call() {
        std::cout<< "Foo -> call()" << std::endl;
    }
};

class Bar : public Func {
public:
    void call() {
        std::cout<< "Bar -> call()" << std::endl;
    }
};

int main(int argc, char** argv) {
    std::vector<Func> functors;

    functors.push_back( Func() );
    functors.push_back( Foo() );
    functors.push_back( Bar() );

    std::vector<Func>::iterator iter;
    for (iter = functors.begin(); iter != functors.end(); ++iter)
        (*iter).call();
}

运行该代码时,它会在我的计算机上产生以下输出:

$ ./test
Func -> call()
Func -> call()
Func -> call()

有什么方法可以确保在这种情况下调用正确的虚函数?我是 C++ 新手,但我最好的猜测是:

(*iter).call();

它被投射到一个Func物体上。它是否正确?

4

7 回答 7

7

您应该使用 shared_ptr 或 unique_ptr 来保存多态类型集合的元素。

现在编写代码时, Foo 和 Bar 实例被强制(复制构造)到 Func 类型的实例中以适应向量。(原因是向量通过固定大小的值立即存储其元素以提高性能,但是多态子类的大小在编译时未知,因此它只能存储基类。)

这个更好:

int main(int argc, char** argv) {
    vector<shared_ptr<Func>> functors;

    functors.push_back( make_shared<Func>() );
    functors.push_back( make_shared<Foo>() );
    functors.push_back( make_shared<Bar>() );

    for (auto functor : functors)
        functor->call();
}

在上面,引用计数指针用于在向量中隐式共享 Func 的异构子类。(此间接允许通过地址间接存储任意大小的 Func 子类。)

此外,您可能想看看 std::function 和 std::bind,而不是滚动您自己的仿函数类型。

要查看的另一件事是完美的转发和可变模板。

更新:对于旧编译器:

int main(int argc, char** argv) {
    vector<std::tr1::shared_ptr<Func> > functors;

    functors.push_back( std::tr1::make_shared<Func>() );
    functors.push_back( std::tr1::make_shared<Foo>() );
    functors.push_back( std::tr1::make_shared<Bar>() );

    for (size_t i = 0; i < functors.size(); ++i)
        functors[i]->call();
}
于 2012-04-14T15:57:04.220 回答
3

向量仅Func按值保存类型,这意味着您的所有时间FooBar被切片并转换为基本Func类型

您需要更改为类似std::vector< Func* >并动态分配派生类实例以使多态调度工作

如果您完全确定在此函数返回后不会将此向量传递给其他函数,作为优化,您可能希望在堆栈中分配实例:

std::vector< Func* > v;
Bar b;
Foo f;
v.push_back( &b);
v.push_back( &f);
于 2012-04-14T15:57:21.613 回答
1

您的 std::vector 正在存储 Func 对象-这意味着当您调用

functors.push_back( Foo() );
functors.push_back( Bar() );

您正在创建对象,Foo然后Bar在将这些对象复制到Func对象时“切片”它们。

如果您想以多态方式使用 Foo 和 Bar,那么更典型的模式是存储某种指针类型的向量(尽管最好不是“原始”指针),例如

std::vector< std::unique_ptr<Func> >

std::vector< std::shared_ptr<Func> >

或者,如果你真的必须......(但前提是你使用的是没有 shared_ptr 或 unique_ptr 的旧编译器)

std::vector< Func* >

于 2012-04-14T15:57:19.410 回答
1

在 C++ 中,多态性仅适用于指针和引用,而向量直接存储对象的实例。当您调用push_back的复制构造函数Func被调用时,它会构建Func存储在向量内的对象。

这称为对象切片,您可以通过 StackOverflow 中的快速搜索了解更多信息。

解决方案是存储指向对象的指针(或者更好的是智能指针),这些对象应该分配到其他地方(可能在堆上)。

于 2012-04-14T15:58:00.433 回答
1

通常,子类的实例可能大于其超类的实例,因此您不应期望子类适合您的向量槽。

并且push_back可能会在内部调用一个复制构造函数(Func类的,因为你有vector<Func>)所以内部向量槽确实Func不属于其他一些类。

于 2012-04-14T15:59:49.280 回答
0

你的问题是你有一个向量Func,但是方法只能通过引用或指针多态地调用。

于 2012-04-14T15:59:28.340 回答
0

有一种非常简单的方法可以在不使用指针的情况下创建向量,方法是创建将继承 A 类和 B 类的子类

#include <iostream>
#include <vector>

using namespace std;

struct BASE {
    string name = "Base class ";
};

struct A : BASE {
     string a = " class a ";
};

struct B : BASE {
     string b = " class b ";
};

struct C : A, B{
    //using base name from A or B// yeah is hardcoded
    string name = A::name;
};

int main()
{

    C c;
    c.a = "s";
    c.b = "h";
    vector<C> vec;

    vec.push_back(c);
    c.b = "j";
    vec.push_back(c);

    cout << vec[0].a << endl;
    cout << vec[0].b << endl;
    cout << vec[0].name << endl;

    cout << vec[1].a << endl;
    cout << vec[1].b << endl;
    cout << vec[1].name << endl;

    return 0;
}
于 2020-12-28T11:28:48.880 回答