2012-01-09 66 views
3

我有一个类A,这是父类B和C. 和A类X,这是一个家长Y和Z.多态性C++,在父类

class A {}; 
class B : public A {}; 
class C : public A {}; 


class X 
{ 
    void foo(A) { std:: cout << "A"; } 
}; 

class Y : public X 
{ 
    void foo(B) {std::cout << "B"; } 
}; 

class Z : public X 
{ 
    void foo(c) {std<<cout <<"C"; } 
}; 

int main() 
{ 
    B b; 
    C c; 

    Y y; 
    Z z; 

    y.foo(b);//prints B // b is a B, and Y::foo takes a B, hence print B 
    y.foo(c);//prints A // mismatch between types, fall back and print A 
    z.foo(b);//prints A // mismatch between types, fall back and print A 
    z.foo(c);//prints C // c is a C, and Y::foo takes a C, hence print C 

    std::vector<A> v; 
    v.push_back(b); 
    v.push_back(c); 

    //In this loop, it always prints A, but *this is what I want to change* 
    for (size_t i = 0; i < v.size(); ++i) 
    { 
     z.foo(v.at(i)); 
     y.foo(v.at(i)); 
    } 
} 
的向量类型的损失

是否可以让项目打印与硬编码呼叫相同的结果? 这意味着我会把它们当作它们的原始类型,而不是它的父类型? 或者一旦我把它们放入A的向量中,它们将永远是A类型的?

+0

'y.foo(C); //打印A'。当然,这不是理想的行为?我想你想让它打印“B”?我认为你需要澄清你想要的行为。给定'p.foo(q)',是否要打印的文本取决于q的类型,或p的类型还是两者?考虑所有9个选项'{x,y,z} .foo({a,b,c});'并告诉我们你期望的行为是什么。 – 2012-01-09 15:02:59

+0

期望的行为是第一次打印 含义y.foo(b)应该打印B,并且y.foo(c)应该调用基类x.foo()并打印A – Bg1987 2012-01-09 16:19:55

+0

OK。所以在'p.foo(q)'中:如果q的(动态)类型与'p :: foo'的参数类型相同,那么应该打印该类型。在所有其他情况下,它应该打印“A”。 – 2012-01-09 16:32:54

回答

6

你所看到的是Object Slicing
您正在将Derived类的对象存储在一个应该存储Base类对象的向量中,这会导致Object切片,并且所存储对象的派生类特定成员被切掉,因此存储在该向量中的对象只是充当基类的对象。

解决方案:

,可以储存指针在矢量基类的反对:

vector<X*> 

通过存储一个指向基类就不会有切片和你能达到预期的多态行为以及通过使功能virtual
正确的做法是使用合适的代替将原始指针存储在向量中的合适的Smart pointer。这将确保您不必手动管理内存,RAII将自动为您做到这一点。

+0

你的意思是,将矢量更改为矢量? – Bg1987 2012-01-09 14:52:36

+0

我相信你也需要将这些函数虚拟为@vasile状态,但是这绝对是切分的。 – greg 2012-01-09 14:55:26

+0

@greg:OP已经声明函数是'virtual'唯一的问题是Op存储对象而不是指针。 – 2012-01-09 14:56:38

1

这叫做切片。当你的元素变成std::vector<A>时,它基本上会将elements复制到A的新构建实例中。因此来自派生类的对象的部分将会丢失(“切掉”)。

为了避免切片你需要使用存储指针而不是元素的容器,所以你应该使用一个std::vector<A*>,或者如果你的元素堆最好分配在某种智能指针(std::shared_ptrstd::unique_ptr的向量C++ 11,boost::shared_ptrstd::tr1::shared_ptr)。

然而书面您的代码将无法正常工作,即使你改变: XYZ都以它们的参数按值,而在你的向量的所有元素都会有型A*,所以他们取消引用将产生A,所以它仍然会调用错误的方法。这可以通过更改签名始终以A&A*并使用dynamic_cast尝试流延入式解决:

class X 
{ 
    void foo(A*) { std:: cout << "A"; } 
}; 

class Y : public X 
{ 
    void foo(A* p) { 
     if (dynamic_cast<B*>(p)) std::cout << "B"; // requires virtual methods in A 
     else     X::foo(p); 
    } 
}; 

class Z : public X 
{ 
    void foo(A*){ 
     if (dynamic_cast<C*>(p)) std::cout << "C"; // requires virtual methods in A 
     else     X::foo(p); 
    } 
}; 

当然dynamic_cast是一个有点昂贵,但如果这是一个问题,你可能要重新考虑你的设计。此外,你还需要确保A, B, C含有一些virtual方法(一virtual destructor将在这里是一个好主意反正),否则dynamic_cast将无法​​正常工作)

+0

我认为你已经钉了它,它应该打印“B”或“C”*当且仅当*参数的动态类型和X,Y,Z所需的类型之间匹配。根据OP的评论,我已经编辑了一些问题。 – 2012-01-09 16:44:00