c++编译设计:安全地扩展类
C++ Compilation Design: Safely extending a class
我有一个关于扩展已经使用的头,源和对象的问题。在理解我的意思之前,你必须接受我想使用这种设计:
在我的一个项目中,我只在头文件中使用函数声明,对于每个定义我使用一个单独的源文件,它将编译成一个单独的目标文件。
假设我在目录"src"中有一个非常简单的类List。
标题可以是:
文件: src/List.hpp
//[guard]
//[includes]
class List {
void add(int value);
void remove(int index);
void clear();
};
现在这三个函数将有独立的文件:
文件: src/清单/add.cpp
void List::add(int value) {
// Do something
}
想象另一个
这些将在某个时刻编译,头文件将在其他编译的类中使用。
假设另一个名为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的类相同?
你的设计看起来可行。然而,我不会推荐它。而且你没有提到模板或标准容器。
我感觉
-
实际上很重要(出于效率的原因)有很多(通常是小的)
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)的小文件都是从间接包含的文件中提取大量源行。
参见这个答案,为什么Google(与D.Novillo一起)努力将准备好的头文件添加到GCC中,为什么LLVM/Clang(与C. latner一起)想要C和c++中的模块。为什么Ocaml、Rust、Go……模块…
您还可以查看GCC生成的GIMPLE表示,或者使用MELT探针(MELT是扩展GCC的特定领域语言,探针是一个简单的图形界面,用于检查GCC的一些内部表示,如GIMPLE),或者使用-fdump-tree-all
选项来检查GCC(小心:该选项产生数百个转储文件)。您也可以将-ftime-report传递给GCC,以便在编译c++代码时更多地了解它在哪里传递时间。
对于机器生成的c++代码,我建议生成更少的文件,但使它们更大。生成数千个几十行的c++小文件是低效的(使总构建时间太长):编译器将花费大量时间一次又一次地解析相同的#include
-d系统头文件,并实例化相同的模板类型(例如,当使用标准容器时)很多次。
请记住,c++允许每个源文件有几个类(与Java相反(内部类除外))。
同样,如果你所有的c++代码都生成了,你真的不需要生成头文件(或者你可能生成一个大的*.hh
),因为你的生成器应该知道哪些类&函数在每个生成的*.cc
中真正使用,并且只能在该文件中生成有用的声明和内联函数定义。
注:注意inline
(和register
一样)只是对编译器的一个(有用的)提示。它可以避免内联标记为inline
的函数(即使在class
定义中是隐式的)。它也可以内联一些没有标记为inline
的函数。然而,编译器需要知道函数体以内联它。
我相信一个函数是否内联是由编译器决定的(见问题我如何知道内联函数是否在它被调用的地方被实际替换?)。要内联一个函数(尽管这并不一定意味着该函数在编译时绝对会内联),您应该在其类中定义该函数,或者在头文件中定义该函数之前使用"inline"命令。例如:
inline int Foo::f(int d) const { return _x+d; };
是的,这很好。这就是静态库的实现方式,因为这使得链接器更容易不拉入不使用的东西。
- 从不同线程使用int64的不同字节安全吗
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 虚拟决赛作为安全
- 获取日期异步信号安全吗?如果在信号处理程序中使用,它会导致死锁吗
- 是否可以通过C++扩展强制多个python进程共享同一内存
- static_assert在宏中,但也可以扩展到可以用作函数参数的东西
- 如何将元素添加到数组的线程安全函数?
- C++中的线程安全删除
- 如何将这个C++哈希表转换为动态扩展和收缩,而不是使用硬设置的最大值
- 扩展光电二极管探测器以支持多个传感器
- 通过网络、跨平台传递std::变体是否安全
- 在std::thread中,joinable()然后join()线程安全吗
- 使用std::istream::peek()总是安全的吗
- 从值小于256的uint16到uint8的Endian安全转换
- C++中的VLA,扩展名为std=C++11
- C++ Python 的扩展 - 安全内存访问和内存布局
- 扩展Windows API结构是安全的吗?
- 为什么签名扩展错误具有很高的安全风险
- 这种使用虚拟受保护方法扩展库的方式是否安全
- 线程安全的TBB可扩展分配器