1

我正在阅读 Scott Meyer 的 Effective C++ 的第 4 项,他试图展示一个在不同翻译单元中使用静态非本地对象的示例。他强调了一个问题,即在一个翻译单元中使用的对象在使用之前不知道它是否已在另一个翻译单元中初始化。如果有人有副本,它在第三版的第 30 页。

这个例子是这样的:

一个文件代表一个库:

class FileSystem{
    public:
        std::size_t numDisks() const;
    ....
};

extern FileSystem tfs;

并在客户端文件中:

class Directory {
    public:
        Directory(some_params);
    ....
};

Directory::Directory(some_params)
{
    ...
    std::size_t disks = tfs.numDisks();
    ...
}

因此,我的两个问题是:

1)如果客户端代码需要使用tfs,那么会有某种包含语句。因此,该代码肯定都在一个翻译单元中吗?我看不出您如何引用不同翻译单元中的代码?程序肯定是一个翻译单元吗?

2)如果客户端代码包含 FileSystem.h,该行extern FileSystem tfs;是否足以让客户端代码调用 tfs(我很欣赏初始化可能存在运行时问题,我只是在谈论编译时范围)?

编辑到第一季度

书中说这两段代码位于不同的翻译单元中。tfs知道它们在不同的翻译单元中,客户端代码如何使用变量?

4

3 回答 3

2

这是一个简化的示例,说明跨多个 TU 的初始化可能会出现问题。

小工具.h:

struct Foo;

extern Foo gadget;

小工具.cpp:

#include <foo.h>
#include <gadget.h>

Foo gadget(true, Blue, 'x');    // initialized here

客户端.cpp:

#include <foo.h>
#include <gadget.h>

int do_something()
{
    int x = gadget.frumple();   // problem!

    return bar(x * 2);
}

问题是不能保证对象在引用它gadget时已经被初始化。do_something()只能保证一个 TU 中的初始化程序在该 TU 中的函数被调用之前完成。

(解决方案是替换extern Foo gadget;Foo & gadget();,在 gadget.cpp 中实现{ static Foo impl; return impl; }并使用gadget().frumple()。)

于 2013-05-05T23:22:54.083 回答
0

1)如果客户端代码需要使用tfs,那么就会有某种include语句。因此,该代码肯定都在一个翻译单元中吗?我看不出您如何引用不同翻译单元中的代码?程序肯定是一个翻译单元吗?

翻译单元(大致)是预处理后的单个 .cpp 文件。编译单个翻译单元后,您将获得一个模块对象(通常具有扩展名.o.obj);在编译所有 TU 之后,它们由链接器链接在一起以形成最终的可执行文件。这通常被 IDE 隐藏(甚至被在命令行上接受多个输入文件的编译器所隐藏),但了解构建 C++ 程序(至少)通过三个步骤完成是至关重要的:预编译、编译和链接。

#include语句将包括类的声明和extern声明,告诉当前的翻译单元该类FileSystem是这样制作的,并且在某些翻译单元中,有一个tfstype的变量FileSystem

2) 如果客户端代码包含 FileSystem.h,则行 extern FileSystem tfs; 足以让客户端代码调用 tfs

是的,extern声明告诉编译器在某些 TU 中有一个这样定义的变量;编译器在目标模块中为其放置一个占位符,链接器在将各种目标模块捆绑在一起时,将使用实际tfs变量的地址(在其他一些翻译单元中定义)来修复它。

请记住,当您编写时,extern您只是在声明一个变量(即您告诉编译器“相信我,某处有这个东西”),当您省略它时,您既是在声明它,也是在定义它(“有这个东西,你必须在这里创建它”)。

函数的区别可能更清楚:当您编写原型时,您正在声明一个函数(“某处有一个函数 x 接受此类参数并返回此类型”),当您实际编写函数(带有函数体)时,您是定义它(“这就是这个函数实际所做的”),并且,如果你之前没有声明它,它也算作一个声明。


有关如何实际使用/管理多个 TU,您可以查看我的这个答案

于 2013-05-05T23:25:54.170 回答
0

这是来自标准 C++03 的示例(我添加了a.hb.h标头):

[basic.start.init]/3

// a.h
struct A { A(); Use(){} };

// b.h
struct B { Use(){} };

// – File 1 –
#include "a.h"
#include "b.h"
B b;
A::A(){
    b.Use();
}

// – File 2 –
#include "a.h"
A a;

// – File 3 –
#include "a.h"
#include "b.h"
extern A a;
extern B b;
int main() {
    a.Use();
    b.Use();
}

在进入 main 之前是否初始化 a 或 b 或者是否延迟初始化直到 a 在 main 中首次使用是由实现定义的。特别是,如果 a 在进入 main 之前初始化,则不能保证 b 在被 a 的初始化使用之前被初始化,即在调用 A::A 之前。然而,如果 a 在 main 的第一条语句之后的某个时刻被初始化,则 b 将在其在 A::A 中使用之前被初始化。

于 2013-05-05T23:42:22.933 回答