课程有外部联系吗?

时间:2011-06-24 08:37:09

标签: c++ external linkage

我有两个文件A.cpp和B.cpp,看起来像

A.cpp
----------
class w
{
public:
    w();
};


B.cpp
-----------
class w
{
public:
    w();
};

现在我读到某个地方(http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref%2Fcplr082.htm),这些类有外部链接。因此,虽然构建我期待多重定义错误,但相反它有点像魅力。但是当我在A.cpp中定义类w时,我得到了重定义错误,这让我相信类有内部联系。

我在这里遗漏了什么吗?

6 个答案:

答案 0 :(得分:21)

正确的答案是肯定的,一个类的名称可能有外部链接。以前的答案是错误的和误导性的。您展示的代码是合法且通用的。

C ++ 03中类的名称可以具有外部链接或没有链接。在C ++ 11中,类的名称可能还有内部链接。

C ++ 03

§3.5[basic.link]

  

当一个名称可能表示同一个对象时,它被认为具有链接,   引用,函数,类型,模板,命名空间或值作为名称   由另一个范围内的声明引入

类名可以有外部链接。

  

具有命名空间作用域的名称如果是名称则具有外部链接   的

     

[...]

     

- 一个命名类(第9节),或在typedef声明中定义的未命名类,其中该类具有用于链接的typedef名称   目的(7.1.3)

班级名称没有联系。

  

这些规则未涵盖的名称没有链接。而且,除了   注意,在本地范围内声明的名称(3.3.2)没有链接。一个名字   没有链接(特别是声明的类或枚举的名称   在本地范围(3.3.2))不得用于声明实体   键。

在C ++ 11中,第一个引用更改,命名空间范围内的类名现在可以具有外部或内部链接。

  

直接或间接声明的未命名命名空间或命名空间   在未命名的命名空间内有内部链接。所有其他名称空间   有外部联系。具有未命名的命名空间范围的名称   给定的内部联系[类名不是]具有相同的联系   作为封闭命名空间,如果它是

的名称      

[...]

     

- 一个命名类(第9节),或在typedef中定义的未命名类   声明,其中类具有用于链接的typedef名称   目的(7.1.3);

第二个引用也会改变,但结论是一样的,类名可能没有联系。

  

这些规则未涵盖的名称没有链接。而且,除了   注意到,在块范围(3.3.3)中声明的名称没有链接。一种   据说当且仅在以下情况下有联系:

     

- 它是一个名为(或具有名称)的类或枚举类型   联系目的(7.1.3))和名称有联系;或

     

- 它是具有链接的类的未命名类或枚举成员;

这里的一些答案将C ++标准中链接的抽象概念与称为链接器的计算机程序混为一谈。 C ++标准对单词符号没有特殊含义。符号是链接器在将目标文件组合为可执行文件时解析的内容。形式上,这与C ++标准中的链接概念无关。该文档仅涉及关于字符编码的脚注中的链接器。

最后,您的示例是合法的C ++,不是ODR违规。请考虑以下事项。

C.h
----------
class w
{
public:
    w();
};


A.cpp
-----------
#include "C.h"


B.cpp
-----------
#include "C.h"

也许这看起来很熟悉。在评估预处理程序指令之后,我们将保留原始示例。 Alok Save提供的维基百科链接甚至将此视为例外。

  

有些东西,比如类型,模板和外部内联函数,可以   在多个翻译单元中定义。对于给定的实体,每个   定义必须相同。

ODR规则考虑了内容。实际上,为了让翻译单元将一个类用作完整类型,您所展示的内容是必需的。

§3.5[basic.def.odr]

  

如果,翻译单元中只需要一个类的定义   该类以需要类类型的方式使用   完整。

编辑 - 詹姆斯·坎泽的答案的后半部分是正确的。

答案 1 :(得分:5)

从技术上讲,正如马克西姆指出的那样,联系适用于符号,而不适用于符号 他们表示的实体。但是符号的联系是部分的 由它表示的内容决定:名称类定义的符号 命名空间范围具有外部链接,w表示同一实体 同时使用A.cppB.cpp

C ++有两组不同的规则 实体:某些实体,如函数或变量,可能只是 在整个程序中定义一次。不止一次定义它们 导致未定义的行为;大多数实现将(大多数 时间,无论如何)给出多重定义错误,但这不是必需的 或保证。其他实体,例如类或模板,是 需要在每个使用它们的翻译单元中定义 进一步要求每个定义相同:相同 令牌序列,以及绑定到同一实体的所有符号,带有 如果是常量表达式中的符号,则非常有限 地址永远不会被采取。违反这些要求也是不明确的 行为,但在这种情况下,大多数系统甚至都不会发出警告。

答案 2 :(得分:0)

外部链接 表示您的整个程序可以访问符号(函数或全局变量), 内部链接 意味着它只能在一个翻译单元中访问。您通过使用extern和static关键字显式控制符号的链接,默认链接对于非const符号是extern,对于const符号是static(internal)。

具有外部链接的名称表示可以通过在同一范围内或在同一翻译单元的其他范围内声明的名称引用的实体(与内部链接一样),或者另外在其他翻译单元中引用。

程序实际上违反了 One Definition Rule ,但编译器很难检测到错误,因为它们位于不同的编译单元中。甚至连接器似乎都无法将其检测为错误。

C ++允许变通方法通过使用 名称空间来绕过单一定义规则。

[更新] 来自C ++ 03标准
§3.2一个定义规则,第5节规定:

在程序中可以存在多个类类型的定义...如果每个定义出现在不同的翻译单元中,并且定义满足以下要求。鉴于这样一个名为D的实体在多个翻译单元中定义,则D的每个定义应由相同的标记序列组成。

答案 3 :(得分:0)

课程没有联系可以迂腐。

链接仅适用于symbols,即函数和变量,或代码和数据。

答案 4 :(得分:0)

声明

class w
{
public:
    w();
};

不会产生任何代码或符号,因此没有任何东西可以链接并且具有“联系”和#34;。但是,当构造函数w()定义 ...

w::w()
{
  // object initialization goes here
}

它将具有外部链接。如果在A.cpp和B.cpp中定义它,将会发生名称冲突;然后会发生什么取决于您的链接器。 MSVC连接子,例如将以LNK2005"已定义的功能错误终止"和/或LNK1169"找到一个或多个多重定义的符号"。 GNU g ++链接器的行为类似。 (对于重复的template方法,它们将取消除一个实例以外的所有方法; GCC文档将其称为" Borland模型")。

有四种方法可以解决此问题:

  1. 如果两个类都相同,则只将定义放在一个.cpp文件中。
  2. 如果您需要两个不同的外部链接class w实现,请将它们放入不同的namespaces
  3. 通过将定义放入匿名命名空间来避免外部链接。
  4. namespace
    {
      w::w()
      {
        // object initialization goes here
      }
    }
    

    匿名命名空间中的每个人都有内部链接,因此您也可以将其用作static声明的替代(这对于类方法是不可能的)。

    1. 通过定义内联方法来避免创建符号:
    2. inline w::w()
      {
        // object initialization goes here
      }
      

      No 4仅在您的类没有静态字段(类变量)时才有效,并且它将复制每个函数调用的内联方法的代码。

答案 5 :(得分:0)

鉴于您不能在类上使用 static,因此提供“类”静态链接的唯一方法是在 anonymous namespace 中定义类型。否则,它将具有外部链接。我将类放在引号中,因为作为类型的类没有链接,而是指类范围中定义的符号的链接(但不是使用类创建的对象的链接)。这包括静态成员和方法以及非静态方法,但不包括非静态成员,因为它们只是类类型定义的一部分,并不额外声明/定义实际符号。

具有静态链接的“类”意味着原本具有外部链接或外部 comdat 链接的成员和方法现在都只有静态链接——它们现在是局部符号,尽管 effect of inline at the compiler level 仍然适用(即,如果在翻译单元中没有引用它,它就不会发出符号)——它不再是汇编程序级别的外部 comdat 符号,而是一个本地符号。即使类的成员或方法是在匿名命名空间之外定义的,它仍然具有静态链接。

如果您在匿名命名空间中声明类类型,您将无法在匿名命名空间之外定义该类型,并且它将无法编译。您需要在翻译单元中的同一个匿名命名空间或不同的匿名命名空间中定义它(不同的匿名命名空间无关紧要,因为它们都组合成同一个匿名匿名命名空间名称 _GLOBAL__N_1)。

这是更改类/结构的成员或方法的链接的唯一方法,因为 static 将使其成为静态成员并且不会更改链接,static 将被忽略行定义,并且 extern 不允许用于类成员/函数。