在 c++ header-only 库中定义 "hidden" 常量

Defining "hidden" constants in c++ header-only libraries

我正在创建库的 C++ header-only 实现,我在其中定义了多个我只想在库中使用的常量(定义为 static constexprs),以生成我的代码更清晰易读。我认为拥有内部链接就足以从我包含该库的任何其他源文件中“隐藏”这些常量。

然而,在我为它编写单元测试的另一个文件中包含这个 header 之后,我得到了像 redefinition of '<constant_name>' 这样的错误(测试源文件有自己的常量名称,因为它们在某些测试用例中很有用,也定义为 static constexprs)。经过进一步阅读,我现在明白内部链接意味着常量在翻译单元之外不可用,但翻译单元包括源文件 以及 任何 header它包括。这可以解释为什么我会收到 redefinition 错误,因为测试源文件和库 header 文件中都存在同名常量,并且它们最终成为一个翻译单元。

目前,我已将所述常量包装在命名空间中以避免冲突。然而,这并不理想。例如,当我使用命名空间作为前缀时,我仍然会在其他文件中获得这些常量的自动完成建议。相反,我希望它们完全隐藏。

我的问题是关于是否有任何解决方法。有没有办法让这些常量只在 header 本身内真正可见,而对包含它的任何文件都不可见?

编辑:这里有一些代码可以证明我的意思。

library.hpp

static constexpr int USEFUL_CONSTANT = 5;

namespace library {
    ...library implementation here...
}

tests.cpp

#include "library.hpp"

// Constant happens to be useful in tests too, but I don't 
// want to expose it for everybody, so I redefine it here
static constexpr int USEFUL_CONSTANT = 5;

...tests here...

我收到重定义错误,因为两个常量都属于同一个翻译单元。

C++ header 文件只是通过 pre-processor.copy-pasted 将源代码“copy-pasted”。

因此,从包含 header 的地方隐藏它们的一种方法是对这些常量使用 pre-processor #define。请注意,在现代 C++ 中使用 #define 被认为是一种不好的做法,只有当它真正使代码更清晰和更易于维护时才应使用它。有这种情况吗?这是相当主观的,但无论好坏,这仍然是实现你想要的东西的一种方法,而且简单易懂。

mylib.h:

#ifndef MYLIB_H
#defein MYLIB_H

// local constants of library, undefined at end
#define MYLIB_CUSTOM_PI (3.2)
// end of local constant defines

//... rest of the code

// undefine local constants
#undef MYLIB_CUSTOM_PI

#endif // MYLIB_H

这具有使用 pre-processor 宏的缺点,但由于您仅在本地使用它们而不将它们公开给库的用户,因此它应该可以正常工作。这仅适用于原始类型(包括 "string literal"const char* 并且编译器将忽略它们)。例如STL容器值不应该用宏定义(因为在每个使用宏的地方都会构造一个新容器)。

几个可行的方法:

  1. 内部命名空间,(boost-style)
namespace mylib
{
    namespace detail
    {
        inline constexpr int const goodenough{42};
    }
    
    int foo(void)
    {
        return detail::goodenough;
    }
}
  1. 转换为某些 class 的 private static 字段,并通过 friend 声明
  2. 授予访问权限
namespace mylib
{
    int foo(void);

    class detail
    {
        friend int ::mylib::foo(void);
 
        static inline constexpr int const goodenough{42};
    };
    
    int foo(void)
    {
        return detail::goodenough;
    }
}

注意:constexpr 意味着 inlineconst