【问题标题】:non-integral constants非整数常数
【发布时间】:2011-01-10 05:52:26
【问题描述】:

我想要一个包含非整数常量的头文件,例如一类。请注意,该常量不需要必须是编译时常量。

static const std::string Ten = "10";

这可以编译但不受欢迎,因为每个编译单元现在都有自己的十个副本。

const std::string Ten = "10";

这将编译,但会因多重定义的十的链接器错误而失败。

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 变量是标准不允许的。

  • c++ 标准中是否有说明 extern 版本应该可以工作,还是我很幸运它可以与 GCC 一起工作?
  • 是否有令人信服的理由不允许内联变量?
  • c++03 有没有更好的方法,还是 c++0x 有更好的方法?

【问题讨论】:

  • 您说您希望“一个文件”拥有它,但又说它不受欢迎,因为它在多个文件中。是哪个?
  • “内联变量”会做什么,而普通的 ol' 常量不会?
  • Caspin,为什么不希望每个编译单元都有自己的 Ten 副本?考虑到“inline const std::string Ten()”版本无论如何都会在每次调用时返回一个新的、单独的对象。
  • @Stefan - 膨胀。这并不重要,但它仍然是不必要的膨胀,通常表明“做错了”。 OP 大概认为他“做错了”,并且像一个理性的人一样,想知道正确的方法。
  • 克里斯,好的,但是臃肿到底在哪里?可执行文件大小?

标签: c++ c++11 constants inline


【解决方案1】:

你好像把它们弄混了。

你说得对

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对象。)

【讨论】:

  • extern 关于最后一个定义?
  • @GMan:当然是可选的。
  • 啊,好的。刚刚阅读了您帖子的前半部分,不知道您可以在上面使用extern 进行定义。这样做的目的是什么?
  • @Gman:如果没有extern,它将定义一个带有内部链接的const对象。但是,先前的 extern 声明的存在覆盖了它。所以定义中的extern 是可选的。我把它放在那里“以防万一”。有些人可能会争辩说它稍微提高了可读性,因为它明确表明它正在定义一个带有 external 链接的const 对象。如果没有extern,不知道先前声明的人可能会得出结论认为链接是内部的。但同样,在这种情况下它是可选的(因为前面的声明)。
  • 这个解决方案在一般情况下不起作用!静态初始化顺序搞砸了。如果常量“A”在 one.cpp 中定义,但用于在另一个.cpp 中初始化常量“B”。初始化可能不正确,这意味着 B 将使用尚未构造的 A 进行初始化。
【解决方案2】:

我不知道 C++ 中是否有更好的方法,但 C 中最好的方法(也适用于 C++)是您列出的方法之一。

有一个单独的编译单元(例如,ten.cpp)只保存数据:

const std::string Ten = "10";

和一个头文件(例如,ten.h)声明它以便可以在其他地方使用:

extern const std::string Ten;

然后你只需要确保任何想要使用它的编译单元都包含头文件(例如ten.h),并且任何想要使用它的可执行文件与单独的编译单元链接(例如ten.o) .

这为您提供了变量的一个 副本,可在任何地方访问。当然,您可以将其在头文件中定义为静态,并且每个编译单元有一份副本。这将简化您需要拥有的文件,而静态将确保没有双重定义的符号。但这不是我推荐的。

我不知道为什么你说:

但我担心如果我误读它会导致链接器错误

这是很久以前公认的做法,如果你想称自己为 C++ 程序员(无意侮辱),你应该知道所有这些东西是如何结合在一起的。

【讨论】:

  • 我以为我只是在头文件中声明了常量十,没有任何定义来备份它。如果在任何时候编译器决定它需要外部的地址,它会发现我从来没有通过在源文件中实际定义变量来提供它。事实证明我错了。提供带有初始化的外部变量就是提供定义。
【解决方案3】:

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 的目的。这对于一个变量来说可能看起来有点笨拙,但对于一个更大的项目,你可能会有一个很好的标题,你可以把它粘贴进去。

【讨论】:

    【解决方案4】:

    以这种方式创建静态用户定义类型是个坏主意。当您有多个此类 UDT 时,您无法控制实例化的顺序。这在小项目中不是问题,但并非所有项目都是小项目。最好让你的静态数据都是普通的旧数据类型——原始指针——并以某种适当的方式初始化它们,以便在程序启动或需要它们时指向你需要的实例。这让您掌控一切。

    您的问题表明这些类型不需要是编译时常量。如果是这样,并且您有一个多线程程序,则您的对象需要保护其状态以防止来自多个线程的同时访问。如果您的某些对象不是线程安全的,那么除了对象本身之外,您还需要一个互斥对象来保护其状态,并且必须具有相同的链接,并且需要初始化。所有这些都会以一种不可接受的方式使您的程序的全局状态变得复杂。

    【讨论】:

    • 常数很好。通常让多个线程读取一个常量是安全的。上面的用法在 java 内存模型中是完全安全的。我很确定它也会在 c++0x 中。唯一的例外是某个地方的某个人可能会修改该值。如果发生这种情况,所有赌注都将取消。我认为我可以通过将其设为 const 来避免这种情况。
    • 我刚刚被初始化顺序所吸引。有些常量在用于初始化其他常量时尚未构造。啊!这比它应该做的要难得多。
    【解决方案5】:

    我认为这里的其他答案更好,但如果您一心想要使用标头进行所有操作,您可以使用简单的包装函数有效地 inline 您的对象(如您特别要求的那样)。

    inline const std::string &get_ten() {
        static const std::string ten = "10";
        return ten;
    }
    

    只有一个string,初始化一次,头文件之外不需要任何东西。

    【讨论】:

    • 这与我提出的简单内联函数基本相同。这种方法的优点是内存定义明确。但是,它不是线程安全的。
    • @Caspin:如果你在争用之前初始化它是线程安全的。
    • 事实证明,所有其他答案都无法处理静态初始化顺序问题。如果常量“A”在 one.cpp 中定义,但用于在另一个.cpp 中初始化常量“B”。初始化可能不正确,这意味着 B 将使用尚未构造的 A 进行初始化。
    猜你喜欢
    • 2014-02-02
    • 1970-01-01
    • 1970-01-01
    • 2011-12-17
    • 1970-01-01
    • 2020-02-23
    • 2010-09-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多