#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. 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.
The function
int sum1 (B* arr)takes a pointer to aBobject as argument according to the signature, but semantically it really takes a pointer to an array ofBobjects. When you callsum1(arrD)you are violating that contract by passing not an array ofBobjects, but rather an array ofDobjects. How do they differ? Pointer arithmetic is done based on the size of the type of the pointer, and aBobject and aDobject have different sizes.An array of
Dis not an array ofBIn 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
Dis that it holds, well,Dobjects, but if a container ofDwas a container ofB, then you would be able to addBobjects (if the argument was extending, you might even consider addingD1objects –also derived fromB!).If instead of raw arrays you were using higher order constructs, like
std::vectorthe compiler would have blocked you from passing astd::vector<D>in place of astd::vector<B>, but why did it not stop you in the case of an array?If an array of
Dis not an array ofB, why did the program compile at all?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 ofint. 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.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
Dobject can be implicitly converted to a pointer toB, sinceBis a base ofD, 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).Now going back to your original code, when you write
sum1( arrD ),arrDis used as an rvalue, and that means that the array decays to a pointer to the first element, so it effectively is translated tosum1( &arrD[0] ). The subexpression&arrD[0]is a pointer, and a pointer is just a pointer…sum1takes a pointer to aB, and a pointer toDis implicitly convertible to a pointer toB, 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 aDin place of aB, but an array ofDis not an array ofB… even if the compiler allowed you to pass it as such.