很长一段时间以来,我一直在编写带有一些程序范围内的常量的代码,例如constants.h
:
const size_t kNumUnits = 4;
const float kAlpha = 0.555;
const double kBeta = 1.2345;
这种方法的问题在于,在分配固定内存块或遍历循环时,通常在较低级别的代码中需要此信息,因此这些单元必须#include这个公共(public)
constants.h
,或者需要传递相关值当调用这些函数时,会使接口(interface)杂乱无章,而这些值在运行时不会改变,并损害了编译器的优化能力。另外,所有这些较低级别的代码都依赖于顶层定义头中的一些常量,这对我来说似乎是一种难闻的气味。它太像全局变量,即使它们都是恒定的。由于需要引用一个或多个常量的每个组件都必须包含公共(public) header ,因此很难编写可重用的代码。将多个组件汇集在一起时,必须手动创建和维护此 header 。两个或多个组件可以使用相同的常量,但是它们本身都不能定义,因为它们在每个程序中都必须使用相同的值(即使两个程序之间的值不同),因此两者都需要#include这个高级头文件以及它碰巧提供的所有其他常量-不太适合封装。这也意味着不能将组件“独立”使用,因为它们需要头文件定义才能工作,但是如果将它们包含在可重用文件中,则在将组件引入主项目时需要手动将其删除。这导致了程序特定的组件头文件的困惑,每次在新程序中使用组件时都需要手动修改它们,而不是简单地从客户端代码中获取指令。
另一种选择是在运行时通过构造函数或其他成员函数提供相关的常量。但是,处理性能对我来说很重要-我有一堆类,它们都对编译时指定固定大小的数组(缓冲区)进行操作。当前,此大小要么从
constants.h
中的常量获取,要么在运行时作为函数参数传递给对象。我一直在进行一些实验,将数组大小指定为非类型模板参数或const
变量,并且似乎编译器可以生成更快的代码,因为循环大小在编译时是固定的,并且可以得到更好的优化。这两个功能很快:const size_t N = 128; // known at compile time
void foo(float * buffer) {
for (size_t i = 0; i < N; ++i) {
buffer *= 0.5f;
}
}
template <size_t N> // specified at compile time
void foo(float * buffer) {
for (size_t i = 0; i < N; ++i) {
buffer *= 0.5f;
}
}
与不能很好地优化的纯运行时版本相反,因为在编译时不知道N:
void foo(float * buffer, size_t N) {
for (size_t i = 0; i < N; ++i) {
buffer *= 0.5f;
}
}
使用非类型模板参数在编译时传递此信息的效果与#包括具有其
const
定义的全局常量文件的性能相同,但封装效果更好,并且允许特定信息(且不再包含)暴露给需要它的组件。因此,我想在声明类型时传递N的值,但这意味着我所有的代码都变成了模板代码(以及将代码移动到需要的.hpp文件中)。而且似乎只允许使用整数非类型参数,因此我不能以这种方式传递float或double常量。这是不允许的:
template <size_t N, float ALPHA>
void foo(float * buffer) {
for (size_t i = 0; i < N; ++i) {
buffer[i] *= ALPHA;
}
}
因此,我的问题是处理此问题的最佳方法是什么?人们如何倾向于组织其程序范围内的常数以减少耦合,同时又获得编译时指定的常数的好处?
编辑:
这是使用类型保存常量值的一种方法,因此可以使用模板参数将其向下传递。常量参数在
System
结构中定义,并作为同名foo
模板参数提供给较低层bar
和System
:在顶层:
#include "foo.h"
struct System {
typedef size_t buffer_size_t;
typedef double alpha_t;
static const buffer_size_t buffer_size = 64;
// static const alpha_t alpha = 3.1415; -- not valid C++?
static alpha_t alpha() { return 3.1415; } -- use a static function instead, hopefully inlined...
};
int main() {
float data[System::buffer_size] = { /* some data */ };
foo<System> f;
f.process(data);
}
在foo.h的中间层中:
// no need to #include anything from above
#include "bar.h"
template <typename System>
class foo {
public:
foo() : alpha_(System::alpha()), bar_() {}
typename System::alpha_t process(float * data) {
bar_.process(data);
return alpha_ * 2.0;
}
private:
const typename System::alpha_t alpha_;
bar<System> bar_;
};
然后在bar.h中的“底部”:
// no need to #include anything 'above'
template <typename System>
class bar {
public:
static const typename System::buffer_size_t buffer_size = System::buffer_size;
bar() {}
void process(float * data) {
for (typename System::buffer_size_t i = 0; i < System::buffer_size; ++i) {
data[i] *= static_cast<float>(System::alpha()); -- hopefully inlined?
}
}
};
这样做确实有一个明显的缺点,那就是将很多我将来的代码转换成带有“系统”参数的模板,以防万一他们需要引用一个常量。它也是冗长且难以阅读的。但这确实消除了对头文件的依赖,因为foo和bar不需要预先了解任何有关System结构的信息。
最佳答案
您的大量描述激发了您为什么需要全局这些常量并首先使用它们的原因。如果许多模块确实需要此信息,我想我只是不会将其视为不良的代码气味。这显然是重要的信息,因此应按原样对待。隐藏它,将其散布或(反对!)将其放在一个以上的位置,就好像是在改变它。添加模板等看起来像是额外的复杂性和开销。
需要考虑的几点:
common.h
位于其中common.h
包括在实现文件中,并且仅在真正需要的地方包括另一个想法(受以上@Keith的评论启发)是您可能要考虑您的分层。例如,如果您有多个组件,并且每个组件在理想情况下应该是独立的,则可以具有这些常量的本地版本,而这些版本恰好是从这些全局常量初始化的。这样,您可以大大减少耦合并提高位置/可见性。
例如,主要组件可以具有自己的接口(interface):
// ProcessWidgets.h
class ProcessWidgets
{
public:
static const float kAlphaFactor;
// ...
};
并在其实现本地化:
// ProcessWidgets.cpp
#include "Common.h"
static const float ProcessWidgets::kAlphaFactor = ::kAlpha;
然后,该组件中的所有代码仅引用
ProcessWidgets::kAlphaFactor
。
关于c++ - C++-有什么方法可以为性能和封装构造常量数据?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15939979/