是否需要定义所有前向声明?
Is it required to define all forward declarations?
总的来说,我想知道像这样包含从未定义的 class 的前向声明的程序在技术上是否合式?
class X;
int main() {}
更具体地说,我想知道是否有这样的模式
// lib.h
#pragma once
struct X {
private:
friend class F;
};
如果 lib.h
属于不包含 class F
的定义的共享库,则 是安全的,也不依赖于另一个包含定义的共享库。
是否有人使用头文件以引用符号 F
结束,这可能会导致加载共享库时出现链接器错误?
是的,代码是 well-formed。
您没有以需要 X
才能完成的方式使用 X
。
仅当您尝试创建对象、使用成员或任何需要定义的不完整类型时,才会出现编译器错误。
不完整的类型没关系,它们只是不……完整。通常你需要的类型只是一个声明,而定义并不重要。
举个例子,考虑一个标记类型,一个模板化类型,其模板参数仅用于从模板创建不同的类型:
#include <type_traits>
#include <iostream>
template <typename tag>
struct tagged_type {
// nothing in here uses tag
// tag is only there to make tagged_type<X> and tagged_type<Y> different types
};
struct tagA;
struct tagB;
int main() {
using A = tagged_type<tagA>;
using B = tagged_type<tagB>;
std::cout << std::is_same_v<A,B>;
}
标签tagA
和tagB
不需要定义。它们仅用作标记,用于区分 A
和 B
。 A
和 B
基本上是同一种类型,但是标签使它们成为不同的类型。
根据标准[basic.odr.def]:
Every program shall contain exactly one definition of every non-inline
function or variable that is odr-used in that program outside of a
discarded statement (8.5.1);
关键部分是odr-used,它是由代码中所有其他可能使用该函数的地方决定的。如果它没有在任何(可能被评估的)表达式中命名,它就不是 odr-used 并且不需要定义。
进一步:
A function is named by an expression or conversion if it is the unique
result of a name lookup or the selected member of a set of overloaded
functions (6.5, 12.4, 12.5) in an overload resolution performed as
part of forming that expression or conversion, unless it is a pure
virtual function and either the expression is not an id-expression
naming the function with an explicitly qualified name or the
expression forms a pointer to member (7.6.2.1).
声明函数不需要定义它。调用它、获取它的地址或任何其他需要知道函数位置的表达式(获取它的地址或调用它)需要该函数存在,否则 linker 将不存在能够 link 那些用于定义的用途。如果没有用处,则不依赖于这些符号。
同样,对于 classes,同样的推理适用。同样,来自标准:
A definition of a class is required to be reachable in every context
in which the class is used in a way that requires the class type to be
complete.
[例子:下面完整的翻译单元是well-formed,
即使它从未定义 X:
struct X; // declare X as a struct type
struct X* x1; // use X in pointer formation
X* x2; // use X in pointer formation
—结束例子]
为了完整起见,标准给出了 class 类型 T 需要完整的原因:
- 定义了类型 T 的对象
- a non-static class 声明了类型 T 的数据成员
- T 用作 new-expression
中分配的类型或数组元素类型
- lvalue-to-rvalue 转换应用于引用类型 T
的对象的左值
- 表达式被(隐式或显式)转换为类型 T
- 不是空指针常量且类型不是 cv void* 的表达式,使用标准转换 dynamic_cast 转换为指向 T 的类型指针或对 T 的引用
static_cast
- a class 成员访问运算符应用于类型 T
的表达式
- typeid 运算符或 sizeof 运算符应用于类型 T 的操作数
- 定义或调用了 return 类型或参数类型为 T 的函数
- 定义了一个 class,其基数 class 类型为 T
- 类型 T 的左值分配给
- 类型 T 是 alignof 表达式的主题
- exception-declaration 具有类型 T、对 T 的引用或指向 T
总的来说,我想知道像这样包含从未定义的 class 的前向声明的程序在技术上是否合式?
class X;
int main() {}
更具体地说,我想知道是否有这样的模式
// lib.h
#pragma once
struct X {
private:
friend class F;
};
如果 lib.h
属于不包含 class F
的定义的共享库,则 是安全的,也不依赖于另一个包含定义的共享库。
是否有人使用头文件以引用符号 F
结束,这可能会导致加载共享库时出现链接器错误?
是的,代码是 well-formed。
您没有以需要 X
才能完成的方式使用 X
。
仅当您尝试创建对象、使用成员或任何需要定义的不完整类型时,才会出现编译器错误。
不完整的类型没关系,它们只是不……完整。通常你需要的类型只是一个声明,而定义并不重要。
举个例子,考虑一个标记类型,一个模板化类型,其模板参数仅用于从模板创建不同的类型:
#include <type_traits>
#include <iostream>
template <typename tag>
struct tagged_type {
// nothing in here uses tag
// tag is only there to make tagged_type<X> and tagged_type<Y> different types
};
struct tagA;
struct tagB;
int main() {
using A = tagged_type<tagA>;
using B = tagged_type<tagB>;
std::cout << std::is_same_v<A,B>;
}
标签tagA
和tagB
不需要定义。它们仅用作标记,用于区分 A
和 B
。 A
和 B
基本上是同一种类型,但是标签使它们成为不同的类型。
根据标准[basic.odr.def]:
Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement (8.5.1);
关键部分是odr-used,它是由代码中所有其他可能使用该函数的地方决定的。如果它没有在任何(可能被评估的)表达式中命名,它就不是 odr-used 并且不需要定义。
进一步:
A function is named by an expression or conversion if it is the unique result of a name lookup or the selected member of a set of overloaded functions (6.5, 12.4, 12.5) in an overload resolution performed as part of forming that expression or conversion, unless it is a pure virtual function and either the expression is not an id-expression naming the function with an explicitly qualified name or the expression forms a pointer to member (7.6.2.1).
声明函数不需要定义它。调用它、获取它的地址或任何其他需要知道函数位置的表达式(获取它的地址或调用它)需要该函数存在,否则 linker 将不存在能够 link 那些用于定义的用途。如果没有用处,则不依赖于这些符号。
同样,对于 classes,同样的推理适用。同样,来自标准:
A definition of a class is required to be reachable in every context in which the class is used in a way that requires the class type to be complete.
[例子:下面完整的翻译单元是well-formed, 即使它从未定义 X:
struct X; // declare X as a struct type
struct X* x1; // use X in pointer formation
X* x2; // use X in pointer formation
—结束例子]
为了完整起见,标准给出了 class 类型 T 需要完整的原因:
- 定义了类型 T 的对象
- a non-static class 声明了类型 T 的数据成员
- T 用作 new-expression 中分配的类型或数组元素类型
- lvalue-to-rvalue 转换应用于引用类型 T 的对象的左值
- 表达式被(隐式或显式)转换为类型 T
- 不是空指针常量且类型不是 cv void* 的表达式,使用标准转换 dynamic_cast 转换为指向 T 的类型指针或对 T 的引用 static_cast
- a class 成员访问运算符应用于类型 T 的表达式
- typeid 运算符或 sizeof 运算符应用于类型 T 的操作数
- 定义或调用了 return 类型或参数类型为 T 的函数
- 定义了一个 class,其基数 class 类型为 T
- 类型 T 的左值分配给
- 类型 T 是 alignof 表达式的主题
- exception-declaration 具有类型 T、对 T 的引用或指向 T