将派生类数组分配给基类指针 [英] Assigning derived class array to base class pointer

查看:55
本文介绍了将派生类数组分配给基类指针的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

#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(这是派生类对象的数组)调用sum1()函数时,它将简单地切断" D :: d,但在这种情况下,它将在arrD中重新排列顺序,并且总结是这样的:10 + 11 + 11 + 12 + 12 + 13 + 13 + 14 + 14 + 15它似乎在arrD [i]的b和d字段之间交替,并且应该只对b字段求和.有人可以解释为什么吗?预先感谢.

The problem is in line 2 of main function. 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. 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.

函数 int sum1(B * arr)根据签名将指向 B 对象的指针作为参数,但是从语义上讲,它实际上采用了指向 B 对象的数组.当您调用 sum1(arrD)时,您传递的不是 B 对象的 array ,而是一个 array D 对象.它们有何不同?指针算术是根据指针类型的大小完成的,并且 B 对象和 D 对象具有不同的大小.

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. 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. 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.

D 的数组不是 B

An array of D is not an array of B

通常,派生类型的容器不是基本类型的容器.如果您考虑一下,那么 D 容器的契约就是它保存了 D 对象,但是如果容器是 D B 的容器,那么您就可以添加 B 对象(如果参数正在扩展,您甚至可以考虑添加 D1 对象-也源自 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!).

如果您不是使用原始数组,而是使用更高阶的结构,例如 std :: vector ,则编译器将阻止您传递 std :: vector< D> 代替 std :: vector< 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?

如果 D 的数组不是 B 的数组,为什么要完全编译该程序?

If an array of D is not an array of B, why did the program compile at all?

对此的答案早于C ++.在C语言中,所有函数参数均按值传递.有人认为您也可以按指针传递,但这只是传递指针按值.但是数组是 large ,因此按值传递数组会非常昂贵.同时,当您动态分配内存时,您使用 pointers ,尽管从概念上讲,当您分配10个 int s时,您正在分配的 array int .C语言的设计者考虑了这一点,并且对按值传递规则进行了例外处理:如果您尝试按值传递数组,则将获得指向第一个元素的指针,并且该指针而不是数组将被传递(类似规则存在于函数中,因此您不能复制函数,因此隐式传递函数会获得指向该函数的指针,然后传递给它).从一开始就在C ++中采用了相同的规则.

The answer to this predates C++. In C, all arguments to functions are passed by value. 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 ints you are allocating an array of 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). The same rules have been in C++ since the beginning.

现在,下一个问题是类型系统无法区分所有指向元素的指针和指向数组一部分的元素的指针.这会带来后果.指向 D 对象的指针可以隐式转换为指向 B 的指针,因为 B D ,并且OO编程的整个对象都能够使用派生类型代替基础对象(当然,出于多态性的目的).

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).

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

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] ). 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]) ). 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.

这篇关于将派生类数组分配给基类指针的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆