简体   繁体   English

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

[英]Why does in-class initialisation of static members violate the ODR?

There are several questions on Stack Overflow along the lines of "why can't I initialise static data members in-class in C++". Stack Overflow上有几个问题,分别是“为什么我不能在C ++中初始化静态数据成员”。 Most answers quote from the standard telling you what you can do; 大多数的答案与标准告诉你,你可以做什么报价; those that attempt to answer why usually point to a link (now seemingly unavailable) [EDIT: actually it is available, see below] on Stroustrup's site where he states that allowing in-class initialisation of static members would violate the One Definition Rule (ODR). 那些试图回答为什么通常指向一个链接(现在似乎不可用)[编辑:实际上它可用,见下文]在Stroustrup的网站上,他声明允许静态成员的类内初始化将违反一个定义规则(ODR) )。

However, these answers seem overly simplistic. 然而,这些答案似乎过于简单化。 The compiler is perfectly able to sort out ODR problems when it wants to. 编译器完全能够在需要时解决ODR问题。 For example, consider the following in a C++ header: 例如,请考虑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";

If I instantiate TemplateExample<0> in multiple translation units, compiler/linker magic kicks in and I get exactly one copy of TemplateExample<0>::str in the final executable. 如果我在多个翻译单元中实例化TemplateExample<0> ,编译器/链接器魔法就会启动,我在最终的可执行文件中只获得了一个TemplateExample<0>::str副本。

So my question is, given that it's obviously possible for the compiler to solve the ODR problem for static members of template classes, why can it not do this for non-template classes too? 所以我的问题是,鉴于编译器显然可以解决模板类的静态成员的ODR问题,为什么它也不能为非模板类​​执行此操作呢?

EDIT : The Stroustrup FAQ response is available here . 编辑 :Stroustrup FAQ响应可在此处获得 The relevant sentence is: 相关的句子是:

However, to avoid complicated linker rules, C++ requires that every object has a unique definition. 但是,为避免复杂的链接器规则,C ++要求每个对象都有唯一的定义。 That rule would be broken if C++ allowed in-class definition of entities that needed to be stored in memory as objects 如果C ++允许将需要作为对象存储在内存中的实体的类内定义,则该规则将被破坏

It seems however that those "complicated linker rules" do exist and are used in the template case, so why not in the simple case too? 然而,似乎那些“复杂的链接器规则”确实存在并且在模板案例中使用,那么为什么不在简单的情况下呢?

The C++ Build structure used to be quite simple. C ++ Build结构过去非常简单。

The compiler built object files which normally contained one class implementation. 编译器构建了通常包含一个类实现的目标文件。 The linker then joined all of the object files together into the executable file. 然后,链接器将所有目标文件连接到可执行文件中。

The One Definition Rule refers to the requirement that each variable (and function) used in the executable only appears in one object file created by the compiler. 单定义规则指的是要求可执行文件中使用的每个变量(和函数)仅出现在编译器创建的一个目标文件中。 All other object files simply have a external prototype references to the variable/function. 所有其他目标文件只有一个外部原型引用变量/函数。

Templates where a very late addition to C++, and require that all the template implementation details are available during each compilation of every object, so that the compiler can do all of it's optimizations - this involves lots of inlining and even more name mangling. 模板,它是C ++的最新添加,并要求在每个对象的每次编译期间都可以使用所有模板实现细节,以便编译器可以完成所有优化 - 这涉及大量内联甚至更多的名称修改。

I hope this answers your question, because it the reason for the ODR rule, and why it doesn't affect templates. 我希望这能回答你的问题,因为它是ODR规则的原因,以及它为什么不影响模板。 Because the linker has almost nothing to do with templates, they are all managed by the compiler. 因为链接器几乎与模板无关,所以它们都由编译器管理。 Excluding the case were use template specialization to push an entire template expansion into one object file, so it can be used in other object files, if they only see the prototypes for the template. 排除案例的是使用模板专门化将整个模板扩展推送到一个目标文件中,因此如果它们只能看到模板的原型,则可以在其他目标文件中使用它。

Edit: 编辑:

Back in the olden days linkers frequently linked object files created with different languages. 回到过去,连接器经常链接用不同语言创建的目标文件。 It was common to link ASM and C, and even after C++ some of that code was still used and that absolutely necessitates the ODR. 链接ASM和C是很常见的,即使在C ++之后仍然使用了一些代码并且绝对需要ODR。 Just because your project is only linking C++ files doesn't mean that's all a linker can do, and so it won't be changed because most projects are now solely C++. 仅仅因为你的项目只链接C ++文件并不意味着所有链接器都可以做,因此它不会被更改,因为大多数项目现在只是C ++。 Even now many device drivers use the linker according to it's more original intention. 即便是现在许多设备驱动程序根据它的原始意图使用链接器。

Answer: 回答:

It seems however that those "complicated linker rules" do exist and are used in the template case, so why not in the simple case too? 然而,似乎那些“复杂的链接器规则”确实存在并且在模板案例中使用,那么为什么不在简单的情况下呢?

The compiler manages the template cases, and just creates weak linker references. 编译器管理模板案例,只创建弱链接器引用。

The linker has nothing to do with templates, they are templates used by the compiler to create code it passes to the linker. 链接器与模板无关 ,它们是编译器用来创建传递给链接器的代码的模板。

So the linker rules are not effected by templates, but the linker rules are still important because ODR is a requirement of ASM and C, which the linker still links, and people other than you do still actually use. 因此链接器规则不受模板的影响,但链接器规则仍然很重要,因为ODR是ASM和C的要求,链接器仍然链接,而您以外的人仍然实际使用。

OK, this following example code demonstrates the difference between a strong and weak linker reference. 好的,以下示例代码演示了强和弱链接器引用之间的区别。 After I will try to explain why changing between the 2 can alter the resulting executable created by a linker. 在我尝试解释为什么在2之间进行更改可以改变链接器创建的结果可执行文件之后。

prototypes.h prototypes.h

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

void part1();
void part2();

file1.cpp 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 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 main.cpp中

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

I accept this example is totally contrived, but hopefully it demonstrates why 'Changing strong to weak linker references is a breaking change'. 我接受这个例子是完全做作的,但希望它能说明为什么“改变强弱的链接器引用是一个突破性的改变”。

Will this compile? 这会编译吗? No, because it has 2 strong references to CLASS::global. 不,因为它有2个强引用CLASS :: global。

If you remove one of the strong references to CLASS::global, will it compile? 如果删除对CLASS :: global的强引用之一,它会编译吗? Yes

What is the value of TEMPLATE::global? TEMPLATE :: global的价值是多少?

What is the value of CLASS::global? CLASS :: global的价值是多少?

The weak reference is undefined because it depends on the link order, which makes it obscure at best and depending on the linker uncontrollable. 弱引用是未定义的,因为它取决于链接顺序,这使得它最多模糊并且取决于链接器不可控制。 This is probably acceptable because it is uncommon not to keep all of the template in a single file, because both prototype and implementation are required together for compilation to work. 这可能是可以接受的,因为不将所有模板保存在单个文件中是不常见的,因为原型和实现都需要一起进行编译才能工作。

However, for Class Static Data Members as they were historically strong references, and not definable within the declaration, it was the rule, and now at least common practice to have the full data declaration with the strong reference in the implementation file. 但是,对于类静态数据成员,因为它们是历史上的强引用,并且在声明中不可定义,所以这是规则,现在至少是通常的做法,在实现文件中使用强引用的完整数据声明。

In fact, because of the linker producing ODR link errors for violations of strong references, it was common practice to have multiple object files (compilation units to be linked), that were linked conditionally to alter behaviour for different hardware and software combinations and sometimes for optimization benefits. 实际上,由于链接器会因违反强引用而产生ODR链接错误,因此通常会有多个目标文件(要链接的编译单元),它们是有条件地链接以改变不同硬件和软件组合的行为,有时也是优化的好处。 Knowing if you made a mistake in your link parameters, you would get an error either saying you had forgotten to select a specialization (no strong reference), or had selected multiple specializations (multiple strong references) 知道你的链接参数是否犯了错误,你会得到一个错误,或者说你忘了选择专业化(没有强引用),或者选择了多个专业化(多个强引用)

You need to remember at the time of the introduction of C++, 8 bit, 16 bit and 32 bit processors were all still valid targets, AMD and Intel had similar but different instruction sets, hardware vendors preferred closed private interfaces to open standards. 你需要记住在引入C ++的时候,8位,16位和32位处理器都是有效的目标,AMD和英特尔有相似但不同的指令集,硬件供应商更喜欢封闭的私有接口来开放标准。 And the build cycle could take hours, days, even a week. 构建周期可能需要数小时,数天甚至一周。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM