为什么静态成员的类内初始化违反了ODR?

Tri*_*dle 12 c++ static-members one-definition-rule language-lawyer in-class-initialization

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

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

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";
Run Code Online (Sandbox Code Playgroud)

如果我TemplateExample<0>在多个翻译单元中实例化,编译器/链接器魔法就会启动并且我TemplateExample<0>::str在最终可执行文件中只获得一个副本.

所以我的问题是,鉴于编译器显然可以解决模板类的静态成员的ODR问题,为什么它也不能为非模板类​​执行此操作呢?

编辑:Stroustrup FAQ响应可在此处获得.相关的句子是:

但是,为避免复杂的链接器规则,C++要求每个对象都有唯一的定义.如果C++允许将需要作为对象存储在内存中的实体的类内定义,则该规则将被破坏

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

Str*_*ngs 1

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

原型.h

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

void part1();
void part2();
Run Code Online (Sandbox Code Playgroud)

文件1.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;
}
Run Code Online (Sandbox Code Playgroud)

文件2.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;
}
Run Code Online (Sandbox Code Playgroud)

主程序

#include <stdio.h>
#include "template.h"
void main()
{
    part1();
    part2();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

TEMPLATE::global 的价值是什么?

CLASS::global 的价值是什么?

弱引用是未定义的,因为它依赖于链接顺序,这使得它充其量是模糊的,并且依赖于链接器而不可控。这可能是可以接受的,因为不将所有模板保存在单个文件中的情况并不常见,因为原型和实现都需要一起才能编译工作。

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

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

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