将派生类数组分配给基类指针

时间:2012-08-21 23:10:22

标签: c++

#include <iostream> 
class B { 
public: 
 B () : b(bCounter++) {} 
int b; 
static int bCounter;  
}; 
int B::bCounter = 0; 
class D : public B { 
public: 
D () : d(bCounter) {} 
int d; 
}; 
const int N = 10; 
B arrB[N]; 
D arrD[N]; 
int sum1 (B* arr) { 
    int s = 0; 
    for (int i=0; i<N; i++) 
         s+=arr[i].b; 
    return s; 
} 
int sum2 (D* arr) { 
     int s = 0; 
     for (int i=0; i<N; i++) s+=arr[i].b+arr[i].d; 
     return s; 
} 
int main() { 
    std::cout << sum1(arrB) << std::endl; 
    std::cout << sum1(arrD) << std::endl; 
    std::cout << sum2(arrD) << std::endl; 
    return 0; 
}

问题出在主要功能的第2行。我期望当使用参数arrD(它是Derived类对象的数组)调用sum1()函数时,它只会“切断”D :: d,但在这种情况下,它会重新排列arrD中的顺序,并且总结如下: 10 + 11 + 11 + 12 + 12 + 13 + 13 + 14 + 14 + 15 它似乎在arrD [i]的b和d字段之间交替,并且它应该仅仅是b字段的总和。 有人可以解释一下原因吗? 提前谢谢。

3 个答案:

答案 0 :(得分:7)

你已经不幸遇到类型系统的一个最佳点,它允许编译完全无效的代码。

函数int sum1 (B* arr)根据签名将B对象的指针作为参数,但从语义上讲,它实际上需要指向{{1}的数组的指针对象。当您致电B时,您通过传递sum1(arrD)个对象的数组,而不是B数组来违反该合同对象。他们有什么不同?指针算法基于指针类型的大小完成,D对象和B对象具有不同的大小。

D数组不是D 的数组

通常,派生类型的容器是基本类型的容器。如果您考虑一下,B容器的合同就是它包含D个对象,但如果D的容器是D的容器。 ,那么你就可以添加B个对象(如果参数扩展了,你甚至可以考虑添加B个对象 - 也来自D1!)。

如果您使用的是更高阶的构造,而不是原始数组,例如B,编译器会阻止您传递std::vector代替std::vector<D>,但为什么不呢?在数组的情况下阻止你?

如果std::vector<B>的数组不是D的数组,为什么程序会编译?

这个问题的答案早于C ++。在C中,函数的所有参数都按值传递。有些人认为你也可以按指针传递,但这只是传递一个指针 by-value 。但是数组 large ,按值传递数组会非常昂贵。同时,当您动态分配内存时,使用指针,尽管从概念上讲,当您使用malloc 10 B时,您正在分配{{1> 的{em>数组 }}。 C语言的设计者考虑了这一点,并对值传递值规则进行了例外处理:如果尝试按值传递数组,则获取指向第一个元素的指针,并传递该指针而不是数组(类似对于函数存在规则,你不能复制函数,因此传递一个函数会隐式获得一个指向该函数的指针并将其传递给它。从一开始就在C ++中使用了相同的规则。

现在,下一个问题是类型系统不会区别于指向元素的指针,而是指向元素的指针,该元素是数组的一部分。这会产生后果。指向int对象的指针可以隐式转换为指向int的指针,因为DB的基础,并且OO编程的整个对象能够使用派生类型代替基础对象(嗯,出于多态的目的)。

现在回到原始代码,当您编写B时,D用作 rvalue ,这意味着数组会衰减到指向第一个元素,因此它被有效地转换为sum1( arrD )。子表达式arrD是一个指针,指针只是一个指针...... sum1( &arrD[0] )获取指向&arrD[0]的指针,指向sum1的指针可以隐式转换为指向B的指针,因此编译器很乐意为您进行转换:D。如果函数只是使用指针并将其用作单个元素,那就没问题,因为您可以传递B代替sum1( static_cast<B*>(&arrD[0]) ),但是D的数组是不是B的数组......即使编译器允许你这样传递它。

答案 1 :(得分:2)

B的尺寸小于D的尺寸。因此,当sum1迭代指针arr时,arr[1]指向它认为是数组中的第二个B元素,它实际上将位于第一个D元素。

所以(假设没有填充),arrD有这样的布局:

arrD: | 2 ints    | 2 ints    | 2 ints    | ...

但是,你设置了一个B *arr,让sum1认为它是一个B数组。所以sum1会认为参数的布局如下:

arr:  | int | int | int | int | int | int | ...

因此,arr[1]实际上是d的{​​{1}}成员。

答案 2 :(得分:2)

您的arr类型为B*,这意味着arr[i](arr + i)会在内存中提升sizeof(B) * i。内存看起来像这样:

  

10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 19 19 20

for循环添加

  

10 11 11 12 12 13 13 14 14 15

这正是内存中的第一个元素,而不是像你想要的那样前进sizeof(D) * i