7

当然,答案是“不”,因为写它的人想了很多,但我想知道为什么。

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

文件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 会在头文件中声明并包含在两者中,但效果如上图。(也就是说,包含标题并不神奇,它只是将标题内容放在该行上。)我们可以编译两者并将它们链接到以下内容:

主文件

#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 的每次编译,sizeof 都是相同的

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

标准布局类是这样的类:

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

由于我们使用的是结构,并且为了彻底,下一个标准说:

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

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

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

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

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

4

1 回答 1

14

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

[基本类型]

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

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

[基本的]

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

在命名空间范围内声明但未在未命名命名空间内(递归地)声明的类名具有外部链接:

[基本.链接]

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

请注意,允许在不同的翻译单元中出现多个类类型的定义,只要定义包含相同的标记序列:

[基本.def.odr]

6 - 如果每个定义出现在不同的翻译单元中,并且 [...] 每个定义 [...]应由相同的令牌序列组成 [...]

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


一些例子:

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

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

这两个Foos 是同一类型。

// 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的相同类型。

于 2014-10-23T14:28:40.107 回答