c ++前向声明,翻译单元,链接

c++ Forward declaration, translation unit, linking

On this site 我读到:

class MyClass;

simply states that "there is such a class" and its full definition will be "coming later" (either in the current file, at compile time, or from some other file at link time)

我不确定我在 link 时是否理解这个过程。我写了下面的代码来演示它。如果我错了,请纠正我。我不确定 link 时间的前向声明是如何工作的。

//first.h
-----------
class Second;

class First{
public:
    Second* ptr;
    First();
};

//first.cpp
-----------
#include "first.h"
extern Second second;
First::First(){ptr = &second;}

//second.h
----------
class Second{
public:
    Second(){};
};

//main.cpp
----------
#include "second.h"
Second second;

int main(int argc, char *argv[])
{
    return 0;
}

此代码已编译。如果行 Second 秒;被评论,linker throws: undefined reference to 'second'。 将 1) 前向声明 2) 编译单元 3) linking 放在一起的一些评论可能会有所帮助。

我认为您阅读的文档过于松散,误导了您:

class MyClass;

并不完全意味着有这样一个class,因为 使 class exist 的唯一方法是定义它,声明是 不是定义。该声明最好理解为:假设有这样一个class

并不 意味着 class 的完整定义将会或不会在以后出现。它是 完整的定义可能需要稍后才能成功编译。要么 不是。如果完整的 class 定义确实需要稍后出现, 它需要才能成功编译;因此在编译时,而不是 linktime.

未定义的引用link你可能引发的年龄错误 通过在 main.cpp 中注释掉 Second second; 只是一个 普通的旧的未定义引用错误,比如你总是会得到 正在尝试 link 一个程序,其中声明了一个变量 extern 在某处被引用而无处定义。它没有必要 与 extern 变量的连接是 class 类型 - 而不是 比说 int - 或前向 class 声明的业务。

classes 的前向声明仅需要抢占 当编译器试图解析的定义时出现死锁 两个相互依赖且无法完成的 classes class 定义完成另一个之前。

一个初级的例子:我天真地写了两个classes firstsecond,其中 每个都有一个使用另一个 class 的对象并调用的方法 方法之一:

first.h

#ifndef FIRST_H
#define FIRST_H

#include <string>
#include <iostream>
#include "second.h"

struct first {

    std::string get_type() const {
        return "First";
    }
    void use_a_second(second const & second) const {
        std::cout << second.get_type() << std::endl;
    }

};

#endif

second.h

#ifndef SECOND_H
#define SECOND_H

#include <string>
#include <iostream>
#include "first.h"

struct second {

    std::string get_type() const {
        return "First";
    }
    void use_a_first(first const & first) const {
        std::cout << first.get_type() << std::endl;
    }
};

#endif

main.cpp

#include "first.h"
#include "second.h"

int main()
{
    first f;
    second s;
    f.use_a_second(s);
    s.use_a_first(f);
    return 0;
}

尝试编译main.cpp:

$ g++ -c -o main.o -Wall -Wextra -pedantic main.cpp 
In file included from first.h:6:0,
                 from main.cpp:1:
second.h:13:19: error: ‘first’ has not been declared
  void use_a_first(first const & first) const {
                   ^~~~~
second.h: In member function ‘void second::use_a_first(const int&) const’:
second.h:14:22: error: request for member ‘get_type’ in ‘first’, which is of non-class type ‘const int’
   std::cout << first.get_type() << std::endl;
                      ^~~~~~~~
main.cpp: In function ‘int main()’:
main.cpp:9:8: error: expected unqualified-id before ‘.’ token
  second.use_a_first(first);

编译器受阻,因为 first.h 包含 second.h,并且 反之,所以无法得到它之前的first的定义 得到second的定义,需要first的定义... 反之亦然.

每个 class 的 定义 之前的前向 声明 另一个,以及每个 class 的相应重构 定义实现,让我们摆脱这个致命的拥抱:

first.h(固定)

#ifndef FIRST_H
#define FIRST_H

#include <string>

struct second; // Declaration

struct first{
    std::string get_type() const {
        return "first";
    }
    void use_a_second(second const & second) const;
};

#endif  

second.h(固定)

#ifndef SECOND_H
#define SECOND_H

#include <string>

struct first; //Declaration

struct second{
    std::string get_type() const {
        return "second";
    }
    void use_a_first(first const & first) const;

};

#endif

first.cpp(新)

#include <iostream>
#include "first.h"
#include "second.h"

void first::use_a_second(second const & second) const {
    std::cout << second.get_type() << std::endl;
}

second.cpp(新)

#include <iostream>
#include "first.h"
#include "second.h"

void second::use_a_first(first const & first) const {
    std::cout << first.get_type() << std::endl;
}

编译:

$ g++ -c -o first.o -Wall -Wextra -pedantic first.cpp
$ g++ -c -o second.o -Wall -Wextra -pedantic second.cpp
$ g++ -c -o main.o -Wall -Wextra -pedantic main.cpp

Link:

$ g++ -o prog main.o first.o second.o

运行:

$ ./prog
second
first

这是前向 class 声明的唯一场景 需要。它可以用于更广泛的环境:参见When can I use a forward declaration?。需要只是每一个需要 编译成功,而不是link年龄。 Link年龄不能尝试直到 编译成功。

文档片段在 定义 一词的使用中也存在误导性的不精确之处。这 class 的定义在编译上下文中意味着一件事,那就是 为了清楚起见,它应该是什么意思。大致意思是别的意思, 在 linkage 的上下文中,为了清楚起见,它 不应该 表示。 在 linkage 的背景下,我们最好只讨论 实现 a class - 甚至是一个需要限定的概念。

就编译器而言,如果 class 来自 defined 从开始到结束:

class foo ... {
    ...
};

没有报错,然后class定义的就是那个span的内容。一个完整的定义 当然,这并不意味着 class 具有完整的 实现 。它 只有当,除了一个完整的定义,所有的方法和 在其定义中声明的静态成员本身也在某处定义,要么 在 class 定义中内联;包含翻译中的脱节 单位,或其他翻译单位(可能在外部编译 库)编译的包含翻译单元得到 linked。 如果这些成员定义中的任何一个未以其中一种方式提供 到 link 时,将导致未解决的引用 linkage 错误。那 是 class 实施 .

的缺陷

linker 的定义 的想法与 C++ 不同 编译器的和更基本的。从 linker 的角度来看, C++ class 实际上并不存在。对于 linker,class 实现被编译器归结为, 一堆符号和符号定义与它得到的没有本质区别 来自任何语言编译器,无论该语言是否处理 classes。 对于 linker 来说,要想成功,重要的是输出二进制文件中引用的所有符号 在同一个二进制文件或请求的动态库中有 定义 在 link 时代。符号(广义上)可以标识一些可执行代码或一些数据。 对于代码符号,定义对linker意味着实现:定义是表示的代码,如果有的话。 对于数据符号,定义对linker表示:它表示表示的数据,如果有的话。

所以当片段说:

.. and its full definition will be "coming later" (either in the current file, at compile time, or from some other file at link time)

这个需要拆开。

class foo 的完整定义必须稍后在 编译 中出现 翻译单元,在类型 foo 之前需要作为任何 else 的类型, 具体来说,基本 class 或 function/method 参数或对象 1 的类型。 如果不满足此要求,将导致 compile 错误:-

  • 如果任何基 class 未完全定义,则无法完全定义 class。
  • 如果函数或方法具有某种类型的参数,则无法对其进行完整定义 未完全定义。
  • 不能存在任何未完全定义的类型的对象。

如果 foonever 以后需要成为基础 class、参数或对象的类型, 那么 class foo 的定义永远不需要遵循声明。

classfoo的完整实现可能需要也可能不需要,或者 由link年龄提供。由于 linker 不知道 classes, 它不知道 完全实现 的 class 与 不完整的 之间的任何区别。 您可以通过添加一个没有实现的方法来更改上面的 class first

struct first{
    std::string get_type() const {
        return "first";
    }
    void use_a_second(second const & second) const;
    void unused();
};

程序会编译,link和运行是一样的。自从 编译器不发出 void first::unused() 的定义,并且由于 该程序不会尝试调用 void first::unused() first 类型的任何对象,或使用其地址,未提及 void first::unused() 出现在 link 时代。如果 我们将 main.cpp 更改为:

#include "first.h"
#include "second.h"

int main()
{
    first f;
    second s;
    f.use_a_second(s);
    s.use_a_first(f);
    f.unused();
    return 0;
}

然后 link 人会在 main.o 中找到对 void first::unused() 的调用 当然还有未解决的参考错误。但这只是 意味着 linkage 无法提供 实现 程序需要。这并不意味着 class 的定义 first 不完整。如果是, main.cpp 的编译将有 失败了,没有 linkage 会被尝试。

要点:-

  • Forward class声明可以避免编译时死锁 相互依赖的 class 定义,以及相应的重构。

  • 前向 class 声明无法避免未解决的引用 linkage 错误。这样的错误总是意味着执行 程序需要代码符号或数据符号的值 link年龄未提供。 class 声明不能添加 link时代的其中一件事。它不会增加 link 年龄。它 只是指示编译器在上下文中容忍 foo foo 成为 class 名称的充分必要条件。

  • Link年龄无法在 link 时间提供 class 定义的任何部分 如果在前向 class 声明之后,class 定义变为 必需的,因为在编译时需要完整的 class 定义或 一点也不。 Linkage 根本无法提供 class 定义的一部分; 只有 class 实现 的元素。


[1] 明确一点:

class foo;
foo & bar();
...
foo * pfoo;
...
foo & rfoo = bar();

可以编译,只需要 class foo 的声明,因为两者都不是 foo * pfoofoo & rfoo 需要类型 foo 的对象存在: 指向 foo 的指针或指向 foo 的引用不是 foo

但是:

class foo;
...
foo f; // Error
...
foo * pfoo; 
...
pfoo->method(); // Error

无法编译,因为f必须是一个foo,并且所寻址的对象pfoo 必须存在,因此是 foo,如果通过它调用任何方法 对象。