C++ 奇怪的C++;减少编译时间的模式
我在Tizen项目的开源代码中发现了可以缩短项目编译时间的模式。它在项目的许多地方都使用 例如,我选择了一个类名C++ 奇怪的C++;减少编译时间的模式,c++,design-patterns,optimization,tizen,compilation-time,C++,Design Patterns,Optimization,Tizen,Compilation Time,我在Tizen项目的开源代码中发现了可以缩短项目编译时间的模式。它在项目的许多地方都使用 例如,我选择了一个类名ClientSubmoduleSupport。它是短的。以下是他们的资料来源: 正如您在上所看到的,它定义了一个ClientSubmoduleSupport,并且定义了一个ClientSubmoduleSupportImplementation类,该类为ClientSubmoduleSupport执行任务 你知道那种模式吗?我很好奇这种方法的优缺点。这种模式被称为“”,也称为“” 意图
ClientSubmoduleSupport
。它是短的。以下是他们的资料来源:
正如您在上所看到的,它定义了一个ClientSubmoduleSupport
,并且定义了一个ClientSubmoduleSupportImplementation
类,该类为ClientSubmoduleSupport
执行任务
你知道那种模式吗?我很好奇这种方法的优缺点。这种模式被称为“”,也称为“”
意图:
“将抽象与其实现分离,以便两者可以独立变化”
《四人帮》设计模式书
您将主要在为第三方开发人员使用的库编写代码时使用此模式,并且永远无法更改API。它使您可以自由更改函数的底层实现,而无需客户在使用新版本的库时重新编译代码
(我已经看到API稳定性要求被写入法律合同中)本节中广泛讨论了使用此模式减少编译时间 拉科斯。“大型C++软件设计”(Addison Wesley,1996).< Herb Sutter也对这种方法的优点进行了一些讨论
.正如前面提到的
sergej
一样,它是Pimpl
惯用语,也是桥
设计模式的一个子集
但我想从C的角度来看待这个话题。我很惊讶,因为我得到了更多的C++,这是一个这样的名字,因为类似的实践在C中应用,有类似的利弊(但由于一个缺少C的东西,额外的PRO)。
C透视图
在C语言中,一个不透明的指针指向一个前向声明的struct
,这是相当常见的做法,如下所示:
// Foo.h:
#ifndef FOO_H
#define FOO_H
struct Foo* foo_create(void);
void foo_destroy(struct Foo* foo);
void foo_do_something(struct Foo* foo);
#endif
// Foo.c:
#include "Foo.h"
struct Foo
{
// ...
};
struct Foo* foo_create(void)
{
return malloc(sizeof(struct Foo));
}
void foo_destroy(struct Foo* foo)
{
free(foo);
}
void foo_do_something(struct Foo* foo)
{
// Do something with foo's state.
}
[Opaque Pointer]-------------------------->[Internal Data Fields]
这与Pimpl
有着相似的优点/缺点,但C还有一个额外的优点/缺点。在C中,structs
没有private
说明符,这是隐藏信息和防止外界访问struct
内部的唯一方法。因此,它成为了一种隐藏和阻止内部访问的手段
<>在C++中,有一个不错的<代码>私下的<代码> >允许我们阻止对内部的访问,然而,除非我们使用类似于Pimpl
的东西,否则我们无法完全隐藏它们的可见性,这基本上是将这种C思想包装起来的,即不透明指针以class
的形式指向前向声明的UDT,并带有一个或多个构造函数和析构函数
效率
独立于唯一上下文的最明显的缺点之一可能是,这种表示将单个连续内存块拆分为两个块,一个用于指针,另一个用于数据字段,如下所示:
// Foo.h:
#ifndef FOO_H
#define FOO_H
struct Foo* foo_create(void);
void foo_destroy(struct Foo* foo);
void foo_do_something(struct Foo* foo);
#endif
// Foo.c:
#include "Foo.h"
struct Foo
{
// ...
};
struct Foo* foo_create(void)
{
return malloc(sizeof(struct Foo));
}
void foo_destroy(struct Foo* foo)
{
free(foo);
}
void foo_do_something(struct Foo* foo)
{
// Do something with foo's state.
}
[Opaque Pointer]-------------------------->[Internal Data Fields]
。。。这通常被描述为引入了一个额外的间接层,但这里的性能问题不是间接层,而是引用的局部性降低,以及堆首次分配和访问这些内部构件时额外的强制缓存未命中和页面错误
有了这个表示,我们也不能再简单地分配
我们需要的所有东西都在堆栈上。只能将指针分配给
堆栈,而堆内构件必须在堆上分配。
如果我们存储一组这些句柄(C中,不透明指针本身,C++中的对象,包含一个对象),则与此相关的性能代价最为明显。在这种情况下,我们最终会得到一个包含(比如)一百万个指针的数组,这些指针可能会指向所有地方,我们最终会以增加页面错误、缓存未命中和堆(空闲存储)分配/释放开销的形式为此付出代价
这最终会给我们带来类似于Java的性能—存储一百万个用户定义类型实例的通用列表并按顺序处理它们(运行和隐藏)。 效率:固定分配器 显著降低(但不是消除)这一成本的一种方法是使用O(1)固定分配器,它为内部提供更连续的内存布局。在我们使用Foo
数组的情况下,这会有很大帮助,例如,通过使用分配器,允许Foo
内部以(更)连续的内存布局存储(改进引用的局部性)
效率:批量接口
一种包含完全不同的设计思维的方法是开始在更粗糙的层次上对公共接口进行建模,使其成为Foo
聚合(一个包含Foo
实例的容器的接口),并隐藏从外部单独实例化Foo
的能力。这仅适用于某些场景,但在这种情况下,我们可以将成本降低到整个容器的单指针间接寻址,如果公共接口由同时在许多隐藏的Foo
对象上运行的高级算法组成,那么这种间接寻址实际上就变得免费了
作为一个明显的例子(尽管希望没有人这样做),我们不想使用Pimpl
策略来隐藏图像单个像素的细节。相反,我们希望在整个图像级别对界面进行建模,该级别由一组像素和应用于一组像素的公共操作组成。一个粒子与一个粒子系统,甚至可能是视频游戏中的一个精灵,都有着相同的想法。如果我们发现自己的性能热点是由于m