我正在阅读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
,那么将会有某种include语句。因此,这个代码肯定都在一个翻译单元中?我不知道你怎么能引用不同翻译单元的代码?当然,程序总是一个翻译单元?
2)如果客户端代码包含FileSystem.h,那么行extern FileSystem tfs;
足以让客户端代码调用tfs(我很欣赏初始化可能存在运行时问题,我只是在谈论编译时间范围)?
编辑到Q1
这本书说这两段代码是在单独的翻译单元中。客户端代码如何使用变量tfs
,知道它们在单独的翻译单元中?
答案 0 :(得分:2)
这是一个简化的例子,说明跨多个TU的初始化是如何产生问题的。
<强> gadget.h:强>
struct Foo;
extern Foo gadget;
<强> gadget.cpp:强>
#include <foo.h>
#include <gadget.h>
Foo gadget(true, Blue, 'x'); // initialized here
<强> client.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()
。)
答案 1 :(得分:0)
1)如果客户端代码需要使用tfs,那么会有某种include语句。因此,这个代码肯定都在一个翻译单元中?我不知道你怎么能引用不同翻译单元的代码?当然,程序总是一个翻译单元?
翻译单元(大致)是预处理后的单个.cpp文件。编译单个翻译单元后,您将获得一个模块对象(通常具有扩展名.o
或.obj
);在编译完所有TU之后,链接器将它们链接在一起以形成最终的可执行文件。这通常由IDE(甚至是编译器在命令行上接受多个输入文件)隐藏,但是理解构建C ++程序至少需要三次传递:预编译,编译和链接是至关重要的。
#include
语句将包括类的声明和extern
声明,告诉当前的翻译单元类FileSystem
是这样做的,并且在某些翻译中单位,tfs
类型的变量FileSystem
。
2)如果客户端代码包含FileSystem.h,则行extern FileSystem tfs;足以让客户端代码调用tfs
是的,extern
声明告诉编译器在某个TU中有一个像这样定义的变量;编译器在对象模块和链接器中放置占位符,当将各种对象模块绑在一起时,将使用实际tfs
变量的地址(在其他一些中定义)来修复它翻译单位)。
请记住,当你编写extern
时,你只是声明一个变量(即你告诉编译器“信任我,在某处有这个东西”),当你省略它时你们都在声明它并定义它(“有这个东西,你必须在这里创建它”)。
区别可能与函数更清楚:当你编写原型时,你声明一个函数(“某处有一个函数x,它接受这样的参数并返回这个类型”),当你真正写的时候函数(使用函数体)你定义它(“这就是这个函数实际上做的”),如果你以前没有声明它,它也算作一个声明。 / p>
有关实际使用/管理多个TU的方法,您可以查看this answer of mine。
答案 2 :(得分:0)
以下是标准C ++ 03中的示例(我添加了a.h
和b.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,还是在main中首次使用a之前是否延迟了初始化。特别是,如果在输入main之前初始化a,则不能保证b在初始化a之前初始化,即在调用A :: A之前。但是,如果a在main的第一个语句之后的某个时刻初始化,则b将在A :: A中使用之前初始化。