Unnamed namespaces,也叫 Anonymous namespaces,是命名空间的一种特殊形式。这种形式可以省略命名空间的名称,如:

namespace { /* .. . */ }

在语义上与等价于:

namespace unique_name { /* ... */ }
using namespace unique_name;

编译器会自动生成一个唯一的名称,并使用 using-directives 自动导入名称。

与其他形式的命名空间不同,Unnamed namespaces 的链接方式是 Internal Linkage,标准描述为:

An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage.

注意,Unnamed namespaces 里面的所有内容都是 Internal Linkage,即便是用 extern

Global static 的作用也是 Internal Linkage,Unnamed namespaces 可以作为它的一种替代方式。主要是 static 所赋予的意义太多,有十几种用法,而 Unnamed namespaces 是一种专门用于控制全局可见性的方式。

C++ Core Guidelines [SF.22] 推荐优先使用 Unnamed namespaces,内容如下:

Nothing external can depend on an entity in a nested unnamed namespace. Consider putting every definition in an implementation source file in an unnamed namespace unless that is defining an “external/exported” entity.

/*-----Bad------*/
static int f();
int g();
static bool h();
int k();
/*-----Good-----*/
namespace {
int f();
bool h();
}
int g();
int k();

LLVM Coding Standards 也推荐使用 Unnamed namespaces,它更加通用,不仅支持变量、函数,也支持类型(static 不能用在 struct/class 前面)。但是,它也有一个问题,就是当代码过长时,很难看出一个随机位置的实体(变量/函数/类型)是否处于 Unnamed namespaces 当中,必须扫描文件中的大部分内容。因此,LLVM 推荐尽量将 Unnamed namespaces 的范围缩小,仅在类声明时使用。

Unnamed namespaces 当然也有其他问题,比如无法在空间之外特化模板:

#include <iostream>

namespace {
    template<typename T>
    void func(T t) {
        std::cout << "Generic template\n";
    }
}

// This is not allowed, as it tries to specialize a template from an anonymous namespace
template<>
void func<double>(double t) {
    std::cout << "Specialized template for double outside anonymous namespace\n";
}

int main() {
    func(42);    // Uses generic template
    func(3.14);  // Uses generic template
}

这也是为何又引入了 inline namespace,否则的话在这种情境下又必须使用 static


2024-07-17 补充:

Google C++ Style Guide 不建议在头文件使用 Unnamed namespaces,描述如下:

Use of internal linkage in .cc files is encouraged for all code that does not need to be referenced elsewhere. Do not use internal linkage in .h files.

那么在头文件中使用有何隐患呢?

首先,可能会违背 ODR。在同一 TU 中,多个 Unnamed namespaces 使用的是同一名称。这意味着以下示例中,a 被定义了两次,一个源文件中引用该头文件将导致 ODR 错误。

// file.h
namespace {
    int a;
}

namespace {
    int a;
}

其次,可能会产生奇怪的结果。例如:

// tu_one.h
namespace {
    int val = 42;
}

// cause unexpected results
inline int get_value() { return val; }

// tu_one.cpp
#include "tu_one.h"
#include <iostream>

void print_tu_one() {
    std::cout << "tu_one value: " <<  get_value() << "\n";
    val = 100;
}

// tu_two.cpp
#include "tu_one.h"
#include <iostream>

void print_tu_two() {
    std::cout << "tu_two value: " << get_value() << "\n";
    val = 200;
}

int main() {
    extern void print_tu_one();

    print_tu_one();
    print_tu_two();
    print_tu_one();
    print_tu_two();
}

一共有两个 TUs,val 处于 Unnamed namespace,属于 Internal Linkage,而 get_value() 属于 External Linkage。两个 TUs 共用同一份 get_value() 函数,而 val 是各有各的,返回结果时将产生不可预料的结果。

测试的结果如下:

tu_one value: 42
tu_two value: 100
tu_one value: 100
tu_two value: 100

最后,还可能会导致代码膨胀。包含 Internal Linkage 的每个 TUs 都会有一份拷贝,致使可执行文件膨胀。

可见,主要原因集中于 Unnamed namespaces 的反直觉行为,放到头文件中易于出错。但万事无绝对,只要能够避免这些潜在的问题,使用也无妨。比如 nlohmann json issue 552 就在头文件中用了如下代码:

/// namespace to hold default `to_json` / `from_json` functions
namespace
{
constexpr const auto& to_json = detail::static_const<detail::to_json_fn>::value;
constexpr const auto& from_json = detail::static_const<detail::from_json_fn>::value;
}

这里是 constexpr 引用对象,而非 constexpr 变量,后者在 file scope 下默认是 Internal Linkage,而前者是 External Linkage,并且不会隐式 const。因此,代码中显式添加了 const,并且将定义全部放在 Unnamed namespace 当中,以改变链接方式。

Leave a Reply

Your email address will not be published. Required fields are marked *

You can use the Markdown in the comment form.