c++11(工作草案)标准中的布局兼容性是否太弱



当然,答案是"不",因为写这本书的人想了很久,但我想知道为什么。

考虑到(无模板)类通常在头文件中声明,然后将其包含在单独编译的几个文件中,再考虑以下两个文件:

文件1.c

#include <cstddef>
struct Foo {
public:
   int pub;
private:
   int priv;
};
size_t getsize1(Foo const &foo) {
  return sizeof(foo);
}

文件2.c

#include <cstddef>
struct Foo {
public:
   int pub;
private:
   int priv;
};
size_t getsize2(Foo const &foo) {
  return sizeof(foo);
}

通常,Foo将在头文件中声明,并包含在两者中,但效果如上所示。(也就是说,包括一个标题并不是什么魔法,它只是把标题的内容放在那一行。)我们可以编译这两个标题,并将它们链接到以下内容:

main.cc

#include <iostream>
struct Foo {
public:
   int pub;
private:
   int priv;
};
size_t getsize1(Foo const &);
size_t getsize2(Foo const &);
int main() {
    Foo foo;
    std::cout << getsize1(foo) << ", " << getsize2(foo) << ", " << sizeof(foo) << 'n';
}

一种方法是使用g++:

g++ -std=c++11 -c -Wall file1.cc 
g++ -std=c++11 -c -Wall file2.cc 
g++ -std=c++11 -c -Wall main.cc 
g++ -std=c++11 -Wall *.o -o main

(关于我的体系结构和环境),这显示了:8,8,8。每次编译file1.cc、file2.cc和main.cc 时,大小都是相同的

但是c++11标准能保证这一点吗?期望与所有3个Foo的布局兼容真的可以吗?Foo同时包含私有和公共字段,因此它不是c++11标准(工作草案)第9条第7段中定义的标准布局结构:

标准布局类是指:

  • 不具有非标准布局类(或此类类型的数组)或引用类型的非静态数据成员
  • 没有虚拟函数(10.3),也没有虚拟基类(10.1)
  • 对所有非静态数据成员具有相同的访问控制(第11条)
  • 没有非标准布局基类
  • 在最派生的类中没有非静态数据成员,并且最多有一个基类具有非静态数据会员,或者没有基类具有非静止数据会员,以及
  • 没有与第一个非静态数据成员类型相同的基类

由于我们使用的是structs,为了彻底起见,下一段说:

标准布局结构是用类键结构或类键类定义的标准布局类。标准布局联合是使用类键联合定义的标准布局类。

据我所知,该标准仅定义了标准布局中结构之间的布局兼容性(第9.2条,第18段)。

如果两个标准布局结构(第9条)类型具有相同数量的非静态数据成员,并且相应的非静态的数据成员(按声明顺序)具有布局兼容的类型(3.9),则它们是布局兼容的

那么,是否可以保证所有三个Foo的布局都兼容,更重要的是为什么?

为什么在编译过程中为Foo创建不同布局的(非确定性)编译器不是c++11编译器?

三个Foo是布局兼容的,因为它们是相同的类型struct ::Foo

[基本类型]

11-如果两个类型T1和T2是同一类型,则T1和T2为布局兼容类型。

类是相同的类型,因为它们具有相同的(完全限定的)名称,并且具有外部链接:

[基本]

9-在多个翻译单元中使用的名称可能指代这些翻译单元中的同一实体,这取决于每个翻译单元中指定的名称的链接(3.5)。

在命名空间范围内声明的类名,如果没有在未命名的命名空间内(递归)声明,则具有外部链接:

[基本链接]

2-当名称可能表示与另一个作用域中的声明引入的名称相同的[…]类型[…]时,称其具有链接:
--当一个名称具有外部链接时,它所表示的实体可以由其他翻译单元的作用域或同一翻译单元的其他作用域的名称引用。[…]
4-未命名命名空间或在未命名命名空间内直接或间接声明的命名空间具有内部链接。所有其他命名空间都有外部链接。如果名称空间作用域的名称是[…]
的名称,则该名称的名称空间范围未在上面给定内部链接,则该名与封闭名称空间具有相同的链接--命名类(第9条),或在typedef声明中定义的未命名类,其中该类具有用于链接目的的typedef名称(7.1.3)[…]

请注意,允许一个类类型的多个定义出现在不同的翻译单元中,只要这些定义由相同的令牌序列组成:

[basic.def.odr]

6-一个程序中可以有一个以上的类类型定义(第9条)[…],前提是每个定义出现在不同的翻译单元中,并且[…]每个定义[…]应由相同的令牌序列组成[…]

因此,如果Foo具有不同的名称,它们将不是同一类型;如果它们出现在匿名命名空间或函数定义中(内联函数除外;请参见[dcl.fct.spec]/4),则它们将没有外部链接,因此不会是同一类型。在任何一种情况下,只有当它们是标准布局时,它们才是布局兼容的。


一些例子:

// tu1.cpp
struct Foo { private: int i; public: int j; };
// tu2.cpp
struct Foo { private: int i; public: int j; };

两个Foo是相同的类型。

// tu1.cpp
struct Foo { private: int i; public: int j; };
// tu2.cpp
struct Foo { private: int i; public: int k; };

ODR违规;未定义的行为。

// tu1.cpp
struct Foo { private: int i; public: int j; };
// tu2.cpp
struct Bar { private: int i; public: int j; };

不同的名字,不同的类型。布局不兼容。

// tu1.cpp
struct Foo { int i; int j; };
// tu2.cpp
struct Bar { int i; int j; };

不同的名称,不同的类型,但布局兼容(自标准布局以来)。

// tu1.cpp
namespace { struct Foo { private: int i; public: int j; }; }
// tu2.cpp
namespace { struct Foo { private: int i; public: int j; }; }

内部联动;不同类型。

// tu1.cpp
static void f() { struct Foo { private: int i; public: int j; }; }
// tu2.cpp
static void f() { struct Foo { private: int i; public: int j; }; }

无联动;不同类型。

// tu1.cpp
inline void f() { struct Foo { private: int i; public: int j; }; }
// tu2.cpp
inline void f() { struct Foo { private: int i; public: int j; }; }

[dcl.fct.spec]/4的类型相同。

最新更新