我有一个关于扩展已使用的标头,源和对象的问题。 在理解我的意思之前,你必须接受我想要使用这个设计:
在我的一个项目中,我只在标题中使用函数声明,而对于每个定义,我使用一个单独的源文件,它将编译为一个单独的目标文件。
假设我在目录“src”中有一个名为List的非常简单的类。
标题可能如下所示:
文件: src / List.hpp
//[guard]
//[includes]
class List {
void add(int value);
void remove(int index);
void clear();
};
现在这三个函数将有单独的文件:
文件: src / List / add.cpp
void List::add(int value) {
// Do something
}
想象另外2。
这些将在某个时刻进行编译,其他编译类中的将 。
假设另一个名为ABC的类使用List的头文件。 对于ABC类中的每个函数,都会生成一个目标文件。
现在我们要调整List的标题,我们不想更改函数,我们只想添加一个函数:
文件: src / List.hpp
//[guard]
//[includes]
class List {
void add(int value);
int find(int value);
void remove(int index);
void clear();
};
因此正在生成另一个源文件和目标文件,在此示例中调用它:src / List / find.cpp和src / List / find.o
现在我的问题是,这是使用标题,来源和对象的合法方式吗? 这会产生问题,还是根本不可能?
此外,ABC类中名为List的类仍然与名为List的新创建的类相同吗?
答案 0 :(得分:1)
您的设计似乎可行。但是,我不会推荐它。你没有提到模板或标准容器。
我的感觉是
实际上(出于效率原因)有很多(通常很小的)inline
函数,特别是内联成员函数(如getter,setter等......),通常包含在他们的class
类 {
.... }
定义。
因此,某些成员函数应该是inline
,或者在类中
class Foo {
int _x;
Foo(int x) : _x(x) {};
~Foo() { _x=0; };
int f(int d) const { return _x + d; };
}
然后所有构造函数Foo::Foo(int)
,析构函数Foo::~Foo
和成员函数int Foo::f(int)
都被内联
或类之后(通常更容易为机器生成的代码),如
class Foo {
int _x;
inline Foo(int x);
inline ~Foo();
inline int f(int d) const;
};
Foo::Foo(int x) { _x = x; };
Foo::~Foo() { _x = 0; };
int Foo::f(int d) const { return _x+d; };
在这两种情况下,出于效率原因,您需要内联(或者链接时间优化,例如gcc -flto -O
进行编译和链接)。
编译器只有在知道其定义(它们的主体)时才能内联函数。
然后,每当你#include
一些类定义。你需要以某种方式获得编译的内联函数定义。你把它放在同一个标题中,或者标题本身#include
一些其他文件(提供内联函数的定义)
一般情况下,特别是在使用标准C ++库(并使用标准容器,例如#include <vector>
)时,您将获得大量系统标头(间接包含)。实际上,您不需要非常小的实现文件(即每个文件只有几十行源代码是不切实际的)。
同样,现有的C ++框架库会提取大量(间接)标头(例如#include <QtGui>
会带来大量代码)。
我建议至少有一千行的C ++源文件(*.hh
或*.cc
)。
查看预处理代码的大小,例如使用g++ -H -C -E
...在实践中你会感到害怕:即使编译了几十行源代码的小C ++文件,你也会有数千条预处理的源代码行。
因此我对千行源文件的建议:任何使用C ++标准库或某些C ++框架库(Boost,Qt)的较小文件都会从间接包含的文件中提取大量源代码。 < / p>
另请参阅this answer,为什么Google(与D.Novillo一起)努力向GCC添加preparsed headers,为什么LLVM / Clang(与C.Latner)在C中需要modules C ++。为什么Ocaml,Rust,Go,......有模块......
您还可以查看GCC生成的GIMPLE表示,使用MELT probe(MELT是一种特定于域的语言来扩展GCC,探针是一个简单的图形界面,用于检查GCC的一些内部表示,如Gimple ),或者使用-fdump-tree-all
选项到GCC(注意:该选项会产生数百个转储文件)。您也可以将-ftime-report传递给GCC,以便在编译C ++代码时更多地了解它的时间。
对于机器生成的C ++代码,我建议更多生成更少的文件,但要使它们更大。生成数十个几十行的小C ++文件是低效的(使总构建时间太长):编译器将花费大量时间一次又一次地解析相同的#include
- d系统头,并实例化相同的模板类型(例如,当使用标准容器时)很多次。
请记住,C ++允许每个源文件有几个类(与Java相反(内部类除外))。
此外,如果生成了所有C ++代码,则实际上不需要生成头文件(或者您可能生成一个大的*.hh
),因为您的生成器应该知道哪些类和&amp;函数实际上在每个生成的*.cc
中使用,并且可以在该文件中仅生成那些有用的声明和内联函数定义。
P.S。:请注意inline
(如register
)只是编译器的一个(有用的)提示。它可以避免内联标记为inline
的函数(甚至隐含地,在class
定义内)。它还可以内联一些未标记为inline
的函数。但是,编译器需要知道函数体以内联它。
答案 1 :(得分:1)
我相信函数是否内联是由编译器决定的(参见问题How will i know whether inline function is actually replaced at the place where it is called or not?)。要内联函数(虽然这并不一定意味着函数将在编译时完全内联),您应该在其类中定义函数,或者在标题之外定义函数之前使用命令“inline”。例如:
inline int Foo::f(int d) const { return _x+d; };
答案 2 :(得分:0)
是的,这很好用。这就是静态库的实现方式,因为这样可以使链接器更容易引入未使用的东西。