我想要一个带有非整数常量的头文件,例如一类。注意常量不需要是编译时常量。
static const std::string Ten = "10";
这会编译但不可取,因为每个编译单元现在都有自己的Ten副本。
const std::string Ten = "10";
这将编译,但会因为多重定义的Ten的链接器错误而失败。
constexpr std::string Ten = "10"s;
这可以工作,但前提是字符串构造函数也是constexpr。它会是但我不能指望每个非整数常量都有一个constexpr构造函数......或者我可以吗?
extern const std::string Ten = "10";
这似乎有效,但如果我错误地呼吸,我恐怕会收到链接器错误。
inline const std::string Ten( ) { return "10"; }
除了干净的语法之外,这还有我想要的一切。另外,现在我必须将常量称为函数调用Ten()
。
inline const std::string = "10";
这似乎是理想的解决方案。当然,标准不允许inline
个变量。
答案 0 :(得分:26)
你好像混淆了他们。
你是对的
static const std::string Ten = "10";
版本。它将“工作”,但它将在每个翻译单元中创建一个单独的对象。
没有static
的版本会产生相同的效果。它不会产生链接器错误,但会在每个转换单元中定义一个单独的对象。在C ++语言中,const
对象默认具有内部链接,这意味着
const std::string Ten = "10"; // `static` is optional
与static
的先前版本完全相同。
包含extern
和初始化程序
extern const std::string Ten = "10"; // it's a definition!
将生成具有外部链接的对象的定义(由于存在初始化程序,它是一个定义)。 此版本将导致链接器错误,因为您将最终得到具有外部链接的对象的多个定义 - 违反ODR。
为了实现您想要实现的目标,您必须在头文件中声明您的常量
extern const std::string Ten; // non-defining declaration
然后在一个且只有一个实现文件
中定义它(使用初始化程序)extern const std::string Ten = "10"; // definition, `extern` optional
(如果常量预先声明为extern
,则定义中的extern
是可选的。即使没有明确的extern
,它也会定义一个带有外部链接的const对象。)
答案 1 :(得分:10)
我不知道C ++中是否有更好的方法,但C中最好的方法(也适用于C ++)是你列出的方法之一。
有一个单独的编译单元(例如,ten.cpp
)只保存数据:
const std::string Ten = "10";
和一个头文件(例如,ten.h
)声明它可以在其他地方使用:
extern const std::string Ten;
然后你必须确保任何想要使用它的编译单元包括头文件(例如,ten.h
),并且任何想要使用它的可执行文件链接到单独的编译单元(例如,{{ 1}})。
这为您提供了一个变量的副本,可以在任何地方访问。当然,你可以在头文件中将其定义为静态,并且每个编译单元有一个副本。这将简化您需要的文件,静态将确保没有双重定义的符号。但这不是我推荐过的。
我不知道为什么你声明:
但是如果我错误地呼吸,我恐怕会收到链接器错误
这是 long 以前的公认实践,如果你想称自己为C ++程序员(没有任何侮辱),你应该知道所有这些事情是如何组合在一起的。
答案 2 :(得分:6)
extern
版本接近你想要的版本。这里:
// in the file tenconstant.cpp
const std::string Ten = "10";
// in the file tenconstant.h
extern const std::string Ten;
// in your file
#include "tenconstant.h"
// do stuff with Ten
您需要为链接器定义一次,这是myconstants.cpp
的目的,但是在您使用它的任何地方都会声明,这是myconstants.h
的目的。对于一个变量来说,这看起来有点笨拙,但对于一个更大的项目,你可能会有一个很好的标题,你可以使用它很多。
答案 3 :(得分:2)
以这种方式创建静态用户定义类型是一个坏主意。当您有多个此类UDT时,无法控制实例化的顺序。这在小型项目中不是问题,但并非所有项目都很小。最好的做法是将静态数据作为所有普通的旧数据类型 - 原始指针 - 并以适当的方式初始化它们,以指向程序启动时所需的实例,或者何时需要它们。这让你掌控一切。
您的问题表明这些类型不需要是编译时常量。如果是这样,并且您有一个多线程程序,则您的对象需要保护其状态不受多个线程的同时访问。如果某些对象不是线程安全的,那么除了对象本身之外,还需要一个互斥对象来保护其状态,并且必须具有相同的链接,并且需要初始化。所有这些使得程序的全局状态变得复杂,这可能是一种不可接受的方式。
答案 4 :(得分:1)
我认为这里的其他答案更好,但是如果您使用标题完成所有操作,您可以使用简单的包装函数有效地inline
您的对象(正如您特别要求的那样)。 / p>
inline const std::string &get_ten() {
static const std::string ten = "10";
return ten;
}
只有一个string
,初始化一次,并且您不需要在头文件之外的任何内容。