考虑以下代码:
#include <iostream>
struct B {
char i;
B(char i) : i(i) {};
void bar() {};
};
struct D : B {
int y;
D(char i, int y) : B(i), y(y) {};
};
void foo(B *arr, size_t size)
{
for(B *end = arr + size; arr < end; ++arr) {
std::cout << arr->i << std::endl;
}
}
int main()
{
D arr[3] = { {'a', 65}, {'b', 66}, {'c', 67} };
foo(arr, sizeof(arr) / sizeof(*arr));
}
正如预期的那样,只打印了
a
。好吧,a
以及基类 i
中 B
后面的两个填充字节。
然后想象一下,我们将
B
的成员函数 bar
设为虚函数。这将使两个类都具有多态性。在此配置中,程序将在 clang 和 gcc 中输出 abc
。这意味着它们都根据多态类型和一些运行时信息来计算偏移量。就我而言,这是无稽之谈。
我还尝试添加另一个具有不同布局的派生类:
struct C : B {
long y;
C(char i, long y) : B(i), y(y) {};
};
//...
C rra[] = { {'a', 65}, {'b', 66}, {'c', 67} };
foo(rra, sizeof(rra) / sizeof(*rra));
在我的例子中,输出是一个奇怪的
apB
,这不是我初始化的结果。所以似乎没有使用运行时信息来计算偏移量。
所以,我的问题非常简单:
我筛选了标准,发现没有提到影响偏移量的运行时信息。
[expr.add] 并没有真正澄清问题。从技术上讲,它表示结果指针应指向数组的元素。
当我打印
foo
时,这对我来说真的很奇怪:
#include <iostream>
struct B {
char i;
B(char i) : i(i) {};
virtual void foo() { std::cout << "I AM BASE" << i << std::endl; };
};
struct D : B {
int y;
D(char i, int y) : B(i), y(y) {};
virtual void foo() { std::cout << "I AM DERIVED" << i << std::endl; };
};
struct C : B {
long y;
C(char i, long y) : B(i), y(y) {};
virtual void foo() { std::cout << "I AM CERIVED" << i << std::endl; };
};
void foo(B *arr, size_t size)
{
for(B *end = arr + size; arr < end; ++arr) {
std::cout << arr->i << std::endl;
arr->foo();
}
}
int main()
{
D arr[] = { {'a', 65}, {'d', 66}, {'c', 67} };
foo(arr, sizeof(arr) / sizeof(*arr));
C rra[] = { {'a', 70}, {'d', 66}, {'c', 67} };
foo(rra, sizeof(rra) / sizeof(*rra));
}
它在第一次迭代中使用正确的
I AM DERIVED
打印 char
,在第二次迭代中仅打印一个 CERIVED
,然后失败并显示 SIGSEGV
。
我可以用最新的和 gcc 来重现它。 godbolt 的链接。
您的
foo()
循环表现出未定义的行为。
它需要一个指向
B[]
数组(第一个元素)的指针,并且只能迭代 B
对象。 但是,您将传入指向 C[]
和 D[]
数组的指针,其中 sizeof(C) > sizeof(B)
和 sizeof(D) > sizeof(B)
,因此迭代使用的指针算术将关闭。 指针算术仅按指针本身类型的大小前进,而不是所指向的对象的大小。
要执行您正在尝试的操作,您必须使用
virtual
方法,并且必须传入指向 B*
指针数组的指针,而不是指向对象数组的指针,例如:
#include <iostream>
struct B {
char i;
B(char i) : i(i) {};
virtual void foo() { std::cout << "I AM BASE: " << i << std::endl; };
};
struct D : B {
int y;
D(char i, int y) : B(i), y(y) {};
void foo() override { std::cout << "I AM DERIVED: " << i << std::endl; };
};
struct C : B {
long y;
C(char i, long y) : B(i), y(y) {};
void foo() override { std::cout << "I AM CERIVED: " << i << std::endl; };
};
void foo(B* arr[], size_t size)
{
for(size_t i = 0; i < size; ++i) {
arr[i]->foo();
}
}
int main()
{
D arr[] = { {'a', 65}, {'d', 66}, {'c', 67} };
B* arr_d[] = { &arr[0], &arr[1], &arr[2] };
foo(arr_d, sizeof(arr_d) / sizeof(*arr_d));
C rra[] = { {'a', 70}, {'d', 66}, {'c', 67} };
B* arr_c[] = { &rra[0], &rra[1], &rra[2] };
foo(arr_c, sizeof(arr_c) / sizeof(*arr_c));
}