【发布时间】:2013-04-03 03:05:01
【问题描述】:
很长一段时间以来,我编写的代码中都定义了一些程序范围的常量,例如 constants.h:
const size_t kNumUnits = 4;
const float kAlpha = 0.555;
const double kBeta = 1.2345;
这种方法的问题在于,在分配固定内存块或迭代循环时,这些信息通常在较低级别的代码中需要,因此这些单元必须#include 这个常见的constants.h,或者相关的调用这些函数时需要传入值,使接口与在运行时永远不会改变的值混淆,并损害编译器的优化能力。
此外,让所有这些低级代码依赖于一些常量的顶级定义标头对我来说似乎是一种难闻的气味。它太像全局变量,尽管它们都是常量。这使得编写可重用代码变得困难,因为需要引用一个或多个常量的每个组件都必须包含公共标头。将一堆组件汇集在一起时,必须手动创建和维护此标头。两个或多个组件可以使用相同的常量,但它们都不能自己定义它,因为它们都必须在每个程序中使用相同的值(即使程序之间的值不同),因此都需要#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;
}
}
使用非类型模板参数在编译时传递此信息与#include the global constants file及其所有const定义具有相同的性能结果,但封装得更好并允许将特定信息(仅此而已)暴露给需要它的组件。
所以我想在声明类型时传入 N 的值,但这意味着我的所有代码都变成了模板代码(并且需要将代码移动到 .hpp 文件)。而且似乎只允许整数非类型参数,所以我不能以这种方式传递浮点或双常量。这是不允许的:
template <size_t N, float ALPHA>
void foo(float * buffer) {
for (size_t i = 0; i < N; ++i) {
buffer[i] *= ALPHA;
}
}
所以我的问题是处理这个问题的最佳方法是什么?人们如何倾向于组织他们的程序范围的常量以减少耦合,同时仍然获得编译时指定常量的好处?
编辑:
这是使用类型来保存常量值的东西,因此能够通过使用模板参数将它们向下传递到层。常量参数定义在System结构体中,并作为同名System模板参数提供给下层foo和bar:
在顶层:
#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 结构的任何信息。
【问题讨论】:
-
您是否对项目进行分层?参照。 en.wikipedia.org/wiki/Layer_%28object-oriented_design%29。 Lakos 是一个很好的参考。
-
有什么理由避免使用#defines?
-
你确定像
void foo(float * buffer, size_t N)这样的函数没有被内联吗? -
不管怎样,你的常量到底是用于什么需要跨多个组件使用的?
-
是的,我正在对我的项目进行分层。我一直在尝试创建一个包含与系统特定方面有关的信息(例如全局缓冲区大小和缓冲区类型)的新类型,然后将其作为模板参数传递给每个较低层,然后将其传递到声明的每个组件。我会用一个例子来更新我的问题。
标签: c++ templates global-variables structure constants