C++ 当S是T的一个平凡子类时,在期望T数组的地方使用S数组安全吗?
考虑以下一对相关结构的声明。子类不添加任何成员变量,唯一的成员函数是一个构造函数,它只将其所有参数转发给基类的构造函数C++ 当S是T的一个平凡子类时,在期望T数组的地方使用S数组安全吗?,c++,arrays,inheritance,C++,Arrays,Inheritance,考虑以下一对相关结构的声明。子类不添加任何成员变量,唯一的成员函数是一个构造函数,它只将其所有参数转发给基类的构造函数 struct Base { Base(int a, char const* b): a(a), b(b) { } int a; char const* b; }; struct Descendant: Base { Descendant(int a, char const* b): Base(a, b) { } }; 现在考虑使用这些
struct Base {
Base(int a, char const* b):
a(a), b(b)
{ }
int a;
char const* b;
};
struct Descendant: Base {
Descendant(int a, char const* b):
Base(a, b)
{ }
};
现在考虑使用这些类型的以下代码。函数
foo
希望接收一个Base
数组。但是,main
定义了一个substant
数组,并将其传递给foo
void foo(Base* array, unsigned len)
{
/* <access contents of array> */
}
int main()
{
Descendant main_array[] = {
Descendant(0, "zero"),
Descendant(1, "one")
};
foo(main_array, 2);
return 0;
}
void foo(基*数组,无符号len)
{
/*。但是,这个问题中的对象是否有可能具有不同的大小?请注意!虽然在您的编译器中几乎可以肯定这一点,但标准并不能保证这一点
至少添加if(sizeof(派生)!=sizeof(基本))logAndAbort(“派生和基本之间的大小不匹配”);检查
如果您想知道,这种安全的编译器是一对一的,其大小不会改变。标准中有一些东西允许派生类与基类不连续。在所有发生这种情况的情况下,大小都必须增加(出于明显的原因).如果声明指向Base的指针数组,则代码将正确运行。
作为奖励,新的foo()可以安全地与具有新数据结构的Base的某些未来子类一起使用
void foo(Base **array, unsigned len)
{
// Example code
for(unsigned i = 0; i < len; ++i)
{
Base *x = array[i];
std::cout << x->a << x->b;
}
}
void do_something()
{
Base *data[2];
data[0] = new Base(1, "a");
data[2] = new Descendent(2, "b");
foo(data, 2);
delete data[0];
delete data[1];
}
void foo(基**数组,无符号len)
{
//示例代码
for(无符号i=0;i
这个程序的行为是否已定义?答案是否取决于foo的主体,比如它是写入数组还是仅从数组中读取
我会冒险回答说程序定义良好(只要foo
是),即使它是用另一种语言编写的(例如C)
如果sizeof(派生)
不等于sizeof(Base)
,则根据前面关于指向派生对象数组的基指针的问题的答案,行为是未定义的。但是,此问题中的对象是否有可能具有不同的大小
我不这么认为。根据我对标准(*)第9.2条第17条的理解
如果两种标准布局结构(第9条)类型具有相同数量的非静态布局,则它们是布局兼容的
数据成员和相应的非静态数据成员(按声明顺序)具有布局兼容性
类型(3.9)
§9第7条至第9条详细说明了布局兼容性的要求:
7标准布局类是指:
- 没有非标准布局类(或此类类型的数组)或引用类型的非静态数据成员
- 没有虚拟函数(10.3)和虚拟基类(10.1)
- 对所有非静态数据成员具有相同的访问控制(第11条)
- 没有非标准布局基类
- 在最派生的类中没有非静态数据成员,并且最多有一个基类具有非静态数据成员,或者没有基类具有非静态数据成员,以及
- 没有与第一个非静态数据成员类型相同的基类
8标准布局结构是用类键struct
或类键class
定义的标准布局类。标准布局联合是用类键联合定义的标准布局类
9[注:标准布局类对于与用其他编程语言编写的代码进行通信非常有用。它们的布局在9.2.-结束说明中有规定]
特别注意最后一条(与§3.9结合)-根据我的阅读,这保证了只要你没有添加太多的“C++内容”(虚拟函数等,从而违反标准布局要求),你的结构/类就会像添加了语法糖的C结构一样运行
如果Base
没有构造函数,会有人怀疑它的合法性吗?我不这么认为,因为这种模式(源自C结构添加构造函数/辅助函数)是惯用的
我愿意接受我错了的可能性,并欢迎添加/更正
(*)我实际上是在看N3290,但实际的标准应该足够接近。虽然这个特定的示例在我熟悉的所有现代平台和编译器上都是安全的,但它通常不安全,而且是一个坏代码的示例
它不能在sizeof(Base)!=sizeof(后代)的编译器/平台上工作
这是不安全的,因为有一天您项目中的某个人将向子类添加新的非静态成员,或者将基类变为虚拟
UPD。基和子体都是标准布局类型。因此,标准要求可以将指向子体的指针正确地重新解释为指向基的指针,这意味着不允许在结构前面填充。
但是在C++的标准中,对于结构的末尾没有填充的要求,所以它依赖于编译器。
还有一个标准建议明确地将此行为标记为未定义。
向子类添加新数据将破坏子类[]
与基[]的互换性
。为了让某些函数假装一个较大结构的数组是一个较小但在其他方面兼容的结构的数组,必须准备一个新数组,其中多余的字节被切掉,在这种情况下,无法定义系统的行为。如果某些指针被切掉会发生什么情况?会发生什么情况