ved*_*edg 17 c++ const constexpr c++11
在C++中声明和定义全局常量的最佳方法是什么?我最感兴趣的是C++ 11标准,因为它在这方面做了很多修复.
[编辑(澄清)]:在这个问题中,"全局常量"表示在编译时在任何范围内已知的常量变量或函数.必须可以从多个翻译单元访问全局常量.它不一定是constexpr风格不变-可以是这样const std::map<int, std::string> m = { { 1, "U" }, { 5, "V" } };或const std::map<int, std::string> * mAddr() { return & m; }.在这个问题中,我没有触及优选的好的范围或常数名称.让我们把这些问题留给另一个问题.[END_EDIT]
我想知道所有不同情况的答案,所以让我们假设这T是以下之一:
typedef int T; // 1
typedef long double T; // 2
typedef std::array<char, 1> T; // 3
typedef std::array<long, 1000> T; // 4
typedef std::string T; // 5
typedef QString T; // 6
class T {
// unspecified amount of code
}; // 7
// Something special
// not mentioned above? // 8
Run Code Online (Sandbox Code Playgroud)
我相信3个可能的范围之间没有大的语义(我不讨论好的命名或范围样式):
// header.hpp
extern const T tv;
T tf(); // Global
namespace Nm {
extern const T tv;
T tf(); // Namespace
}
struct Cl {
static const T tv;
static T tf(); // Class
};
Run Code Online (Sandbox Code Playgroud)
但如果从下面的替代方案中选择更好的方法取决于上述声明范围之间的差异,请指出.
还要考虑在常量定义中使用函数调用的情况,例如<some value>==f();.如何在常量初始化中调用函数会影响备选方案之间的选择?
让我们考虑T与constexpr第一个构造函数.明显的替代方案是:
// header.hpp
namespace Ns {
constexpr T A = <some value>;
constexpr T B() { return <some value>; }
inline const T & C() { static constexpr T t = <some value>; return t; }
const T & D();
}
// source.cpp
const T & Ns::D() { static constexpr T t = <some value>; return t; }
Run Code Online (Sandbox Code Playgroud)
我相信A并且B最适合小型T(例如,有多个实例或在运行时复制它不是问题),例如1-3,有时7.C并且D是更好,如果T是大的,例如4,有时7.
T没有constexpr构造函数.备择方案:
// header.hpp
namespace Ns {
extern const T a;
inline T b() { return <some value>; }
inline const T & c() { static const T t = <some value>; return t; }
const T & d();
}
// source.cpp
extern const T Ns::a = <some value>;
const T & Ns::d() { static const T t = <some value>; return t; }
Run Code Online (Sandbox Code Playgroud)
我通常不会使用a因为静态初始化命令惨败.据我所知,自C++ 11以来b,c它d是非常安全的,甚至是线程安全的.b除非T有一个非常便宜的构造函数,这似乎不是一个好的选择,这对非constexpr构造函数来说并不常见.我可以说出一个c过度的优点d- 没有函数调用(运行时性能); 当常量值改变时,d过度c重新编译的一个优点(这些优点也适用于C和D).我相信我错过了很多推理.请在答案中提供其他考虑因素.
如果你想修改/测试上面的代码,你可以使用我的测试文件(只有header.hpp,source.cpp和上面代码片段的可编译版本,main.cpp从header.hpp打印常量):https:// docs.google.com/uc?export=download&id=0B0F-aqLyFk_PVUtSRnZWWnd4Tjg
我相信以下声明地点之间没有太大区别:
这在许多方面都是错误的.
第一个声明污染全局命名空间; 你从未被使用过的名字"电视",没有误解的可能性.这可能会导致阴影警告,它可能导致链接器错误,它可能会导致使用标头的任何人混淆.它也可能会导致与不使用标题的人发生问题,导致与其他人碰巧也会碰巧使用您的变量名称作为全局变量.
在现代C++中不推荐这种方法,但在C中无处不在,因此导致在.c文件(文件范围)中对"全局"变量使用静态关键字.
第二个声明污染了命名空间; 这不是一个问题,因为命名空间可以自由地重新命名,并且可以免费制作.只要两个项目使用自己的,相对特定的命名空间,就不会发生冲突.在发生此类冲突的情况下,可以重命名每个冲突的名称空间以避免任何问题.
这是更现代的C++ 03风格,而C++ 11通过重命名模板大大扩展了这种策略.
第三种方法是结构,而不是类; 它们有差异,特别是如果你想保持与C的兼容性.类范围复合对命名空间范围的好处; 您不仅可以轻松地封装多个内容并使用特定名称,还可以通过方法和信息隐藏来增加封装,从而大大扩展代码的实用性.这主要是课程的好处,无论范围的好处如何.
您几乎肯定不会使用第一个,除非您的函数和变量非常宽泛且类似STL/STD,或者您的程序非常小并且不太可能嵌入或重用.
我们现在来看看你的情况.
如果构造函数返回常量表达式,则构造函数的大小并不重要; 所有代码都应该在编译时可执行.这意味着复杂性没有意义; 它将始终编译为单个常量返回值.你几乎肯定不会使用C或D; 所做的只是使constexpr的优化不起作用.我会使用A和B中的任何一个看起来更优雅,可能一个简单的赋值是A,复杂的常量表达式是B.
这些都不一定是线程安全的; 构造函数的内容将决定线程和异常安全性,并且很容易使这些语句中的任何一个都不是线程安全的.实际上,A最有可能是线程安全的; 只要在调用main之前不访问该对象,它就应该完全形成; 你的任何其他例子都不能说同样的话.至于你对B的分析,根据我的经验,大多数构造函数(特别是异常安全的构造函数)都很便宜,因为它们避免了分配.在这种情况下,您的任何案件之间不太可能存在太大差异.
我强烈建议你不要再尝试这样的微优化了,也许会对C++习语有更深刻的理解.您尝试在此处执行的大多数操作不太可能导致性能提升.
| 归档时间: |
|
| 查看次数: |
7252 次 |
| 最近记录: |