简体   繁体   English

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

[英]Assigning derived class array to base class pointer

#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; 
}

The problem is in line 2 of main function. 问题出在主要功能的第2行。 I expected that when sum1() function was called with argument arrD( which is an array of the Derived class objects), it would simply "cut off" the D::d, but in this case it rearranges the order in arrD, and the summing goes like this: 10+11+11+12+12+13+13+14+14+15 It seems to be alternating between b and d fields of arrD[i], and it should be summing up only b fields. 我期望当使用参数arrD(它是Derived类对象的数组)调用sum1()函数时,它只会“切断”D :: d,但在这种情况下,它会重新排列arrD中的顺序,并且求和是这样的:10 + 11 + 11 + 12 + 12 + 13 + 13 + 14 + 14 + 15它似乎在arrD [i]的b和d场之间交替,它应该只是b场的总和。 Can someone please explain why? 有人可以解释一下原因吗? Thanks in advance. 提前致谢。

You have been unlucky enough to hit one of the sweet spots of the type system that allows to compile perfectly invalid code. 你已经不幸遇到类型系统的一个最佳点,它允许编译完全无效的代码。

The function int sum1 (B* arr) takes a pointer to a B object as argument according to the signature, but semantically it really takes a pointer to an array of B objects. 函数int sum1 (B* arr)根据签名将一个指向B对象的指针作为参数,但从语义上讲,它实际上是一个指向B对象数组的指针。 When you call sum1(arrD) you are violating that contract by passing not an array of B objects, but rather an array of D objects. 当你调用sum1(arrD)你是不是传递一个数组违反该合同B对象,而是组成的数组 D的对象。 How do they differ? 他们有什么不同? Pointer arithmetic is done based on the size of the type of the pointer, and a B object and a D object have different sizes. 指针算法基于指针类型的大小完成, B对象和D对象具有不同的大小。

An array of D is not an array of B D的数组不是B的数组

In general, a container of a derived type is not a container of the base type. 通常,派生类型的容器不是基本类型的容器。 If you think about it, the contract of a container of D is that it holds, well, D objects, but if a container of D was a container of B , then you would be able to add B objects (if the argument was extending, you might even consider adding D1 objects --also derived from B !). 如果你考虑一下, D的容器的合同就是它拥有D对象,但如果D的容器是B的容器,那么你就可以添加B对象(如果参数是扩展的话) ,您甚至可以考虑添加D1对象 - 也来自B !)。

If instead of raw arrays you were using higher order constructs, like std::vector the compiler would have blocked you from passing a std::vector<D> in place of a std::vector<B> , but why did it not stop you in the case of an array? 如果你使用更高阶的构造来代替原始数组,比如std::vector ,编译器会阻止你传递std::vector<D>来代替std::vector<B> ,但是为什么它没有在阵列的情况下阻止你?

If an array of D is not an array of B , why did the program compile at all? 如果D的数组不是B的数组,为什么程序会编译?

The answer to this predates C++. 对此的回答早于C ++。 In C, all arguments to functions are passed by value. 在C中,函数的所有参数都按值传递。 Some people consider that you can also pass-by-pointer , but that is just passing a pointer by-value . 有些人认为你也可以传递指针 ,但这只是传递一个指针 But arrays are large , and it would be very expensive to pass arrays by value. 但是数组很大 ,按值传递数组会非常昂贵。 At the same time, when you dynamically allocate memory you use pointers , although conceptually, when you malloc 10 int s you are allocating an array of int . 同时,当您动态分配内存时,您使用指针 ,虽然从概念上讲,当您使用malloc 10 int您正在分配一个int 数组 The designers of the C language considered this and made an exception to the pass by value rules: if you try to pass an array by value, a pointer to the first element is obtained, and that pointer is passed instead of the array (a similar rule exists for functions, you cannot copy a function, so passing a function implicitly obtains a pointer to the function and passes that instead). C语言的设计者考虑了这一点,并对值传递值规则进行了例外处理:如果尝试按值传递数组,则获取指向第一个元素的指针,并传递该指针而不是数组(类似函数存在规则,你不能复制一个函数,所以传递一个函数隐式获得一个指向函数的指针,然后传递它。 The same rules have been in C++ since the beginning. 从一开始就在C ++中使用了相同的规则。

Now, the next problem is that the type system does not differentiate from a pointer to an element when that is all there is, and a pointer to an element that is part of an array. 现在,下一个问题是类型系统不能区分指向元素的指针,而是指向元素的指针,指向元素的一部分。 And this has consequences. 这会产生后果。 A pointer to a D object can be implicitly converted to a pointer to B , since B is a base of D , and the whole object of OO programming is being able to use derived types in place of base objects (well, that for the purpose of polymorphism). 指向D对象的指针可以隐式转换为指向B的指针,因为BD的基础,并且OO编程的整个对象能够使用派生类型代替基础对象(嗯,用于此目的)多态性)。

Now going back to your original code, when you write sum1( arrD ) , arrD is used as an rvalue , and that means that the array decays to a pointer to the first element, so it effectively is translated to sum1( &arrD[0] ) . 现在回到原始代码,当你写sum1( arrD )arrD被用作右值 ,这意味着数组衰减到指向第一个元素的指针,所以它实际上被转换为sum1( &arrD[0] ) The subexpression &arrD[0] is a pointer, and a pointer is just a pointer... sum1 takes a pointer to a B , and a pointer to D is implicitly convertible to a pointer to B , so the compiler gladly does that conversion for you: sum1( static_cast<B*>(&arrD[0]) ) . 子表达式&arrD[0]是一个指针,一个指针只是一个指针... sum1是一个指向B的指针,一个指向D的指针可以隐式转换为一个指向B的指针,所以编译器很乐意为你: sum1( static_cast<B*>(&arrD[0]) ) If the function just took the pointer and used it as a single element, that would be fine, as you can pass a D in place of a B , but an array of D is not an array of B ... even if the compiler allowed you to pass it as such. 如果函数只使用指针并将其用作单个元素,那就没问题了,因为你可以传递D来代替B ,但D的数组不是B的数组......即使编译器也是如此允许你这样传递它。

The size of a B is smaller than a size of a D . B的大小小于D的大小。 So when sum1 is iterating over the pointer arr , arr[1] is pointing at what it thinks is the 2nd B element in the array, which will actually be in the middle of the 1st D element. 因此,当sum1迭代指针arrarr[1]指向它认为是数组中的第二个B元素,它实际上位于第一个D元素的中间。

So (assuming no padding), arrD has a layout like this: 所以(假设没有填充), arrD有这样的布局:

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

But, you set a B *arr to it, making sum1 think it is an array of B. So sum1 will think that the parameter has a layout like this: 但是,你设置一个B *arr ,使sum1认为它是一个B的数组。所以sum1会认为参数有这样的布局:

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

So, arr[1] is actually the d member of arrD[0] . 所以, arr[1]实际上是arrD[0]d成员。

Your arr is of type B* , what this means is that arr[i] or (arr + i) would advance sizeof(B) * i in memory. 你的arrB*类型,这意味着arr[i](arr + i)会在内存中提升sizeof(B) * i The memory looks like this: 内存看起来像这样:

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

And the for loop adds 并且for循环增加了

10 11 11 12 12 13 13 14 14 15 10 11 11 12 12 13 13 14 14 15

which is exactly what is the first elements in memory are, instead of advancing by the sizeof(D) * i like you want it. 这正是内存中的第一个元素,而不是像sizeof(D) * i那样前进sizeof(D) * i喜欢你想要它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM