c++ - 为什么静态成员的类内初始化会违反 ODR?

标签 c++ static-members language-lawyer one-definition-rule in-class-initialization

Stack Overflow 上有几个问题,类似于“为什么我不能在 C++ 中初始化静态数据成员”。大多数答案都引用标准告诉你什么你可以做什么;那些试图回答为什么的人通常指向一个链接(现在似乎不可用)[编辑:实际上它是可用的,见下文]在 Stroustrup 的网站上,他声明允许静态成员的类内初始化会违反单一定义规则 (ODR)。

但是,这些答案似乎过于简单化。编译器完全能够在需要时解决 ODR 问题。例如,考虑 C++ header 中的以下内容:

struct SimpleExample
{
    static const std::string str;
};

// This must appear in exactly one TU, not a header, or else violate the ODR
// const std::string SimpleExample::str = "String 1";

template <int I>
struct TemplateExample
{
    static const std::string str;
};

// But this is fine in a header
template <int I>
const std::string TemplateExample<I>::str = "String 2";

如果我实例化 TemplateExample<0>在多个翻译单元中,编译器/链接器魔法开始了,我得到了一份 TemplateExample<0>::str在最终的可执行文件中。

所以我的问题是,鉴于编译器显然可以解决模板类的静态成员的 ODR 问题,为什么它不能对非模板类也这样做?

编辑:Stroustrup 常见问题解答可用here .相关句子是:

However, to avoid complicated linker rules, C++ requires that every object has a unique definition. That rule would be broken if C++ allowed in-class definition of entities that needed to be stored in memory as objects

然而,那些“复杂的链接器规则”似乎确实存在并且在模板案例中使用,那么为什么不在简单案例中呢?

最佳答案

好的,下面的示例代码演示了强链接器引用和弱链接器引用之间的区别。之后我将尝试解释为什么在 2 之间进行更改会改变由链接器创建的生成的可执行文件。

原型(prototype).h

class CLASS
{
public:
    static const int global;
};
template <class T>
class TEMPLATE
{
public:
    static const int global;
};

void part1();
void part2();

file1.cpp

#include <iostream>
#include "template.h"
const int CLASS::global = 11;
template <class T>
const int TEMPLATE<T>::global = 21;
void part1()
{
    std::cout << TEMPLATE<int>::global << std::endl;
    std::cout << CLASS::global << std::endl;
}

file2.cpp

#include <iostream>
#include "template.h"
const int CLASS::global = 21;
template <class T>
const int TEMPLATE<T>::global = 22;
void part2()
{
    std::cout << TEMPLATE<int>::global << std::endl;
    std::cout << CLASS::global << std::endl;
}

main.cpp

#include <stdio.h>
#include "template.h"
void main()
{
    part1();
    part2();
}

我接受这个示例完全是人为的,但希望它能够说明为什么“将强链接器引用更改为弱链接器引用是一项重大更改”。

这会编译吗?不,因为它有 2 个对 CLASS::global 的强引用。

如果您删除对 CLASS::global 的强引用之一,它会编译吗?是的

TEMPLATE::global 的值是什么?

CLASS::global 的值是什么?

弱引用是undefined,因为它依赖于链接顺序,这使得它充其量是模糊的,并且依赖于链接器无法控制。这可能是可以接受的,因为不将所有模板保存在一个文件中是不常见的,因为编译工作需要原型(prototype)和实现。

但是,对于类静态数据成员,因为它们在历史上是强引用,并且不能在声明中定义,因此在实现文件中具有带有强引用的完整数据声明是规则,现在至少是常见的做法。

事实上,由于链接器会因违反强引用而产生 ODR 链接错误,因此通常的做法是拥有多个目标文件(要链接的编译单元),它们有条件地链接以改变不同硬件和软件组合的行为有时是为了优化好处。知道您是否在链接参数中犯了错误,您会收到一个错误,提示您忘记选择特化(无强引用),或选择了多个特化(多个强引用)

您需要记住,在引入 C++ 时,8 位、16 位和 32 位处理器仍然是有效的目标,AMD 和 Intel 有相似但不同的指令集,硬件供应商更喜欢封闭的私有(private)接口(interface)而不是开放标准.构建周期可能需要数小时、数天甚至一周。

关于c++ - 为什么静态成员的类内初始化会违反 ODR?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18909608/

相关文章:

c++ - 从 10 以外的其他基数的输入流中读取

language-lawyer - 在 quoted-printable 中,根据 76 个字符规则,什么构成换行符?

c++ - 运行时的策略模式?

c++ - DirectX 10 中多重采样的操作顺序

c++ - 如何使字符串相等?

c# - 获取静态 DateTime 值的最佳方法

c++ - 静态成员不允许使用不完整的类型

c++ - 谷歌测试和静态局部变量

c++ - 根据标准,整数中的值表示位数?

c++ - 统一初始化中的尾随逗号