【C++】匿名名字空间与内部连接

(点击上方公众号,可快速关注)

连接性linkage)控制某个名字(可以是变量、函数、类等)在多个编译单元的共享,其在C++标准中的描述是异常复杂的。总体上,一个名字的连接性可以是以下三类中的一个:

  • 外部连接(external linkage)

    一个名字可以被其他编译单元使用,我们通常使用头文件引入的名字都具有外部连接,使得名字可以在不同的编译单元内使用。

  • 内部连接(internal linkage)

    一个名字可以在本编译单元内的任意代码使用,这是本篇要说的内容。

  • 无连接(no linkage)

    一个名字只能在某个代码块内使用,如常见的函数体,for、while代码块等。

内部连接在C中的表达

C语言中,如果要使某个名字只在本编译单元内使用,而不暴露给其他编译单元,可以使用static修饰该名字,如下示例代码:

// parse.h
#pragma once


#include <stdbool.h>
#include <stddef.h>


//检查协议的有效性
bool check_protocol_xx(const char* data, size_t len);
// parse.c
#include "parse.h"


// 检查协议头
static bool check_header(const char* data, size_t len) {
    //省略处理代码
    return true;
}


// 检查协议包体
static bool check_body(const char* data, size_t len) {
    //省略处理代码
    return true;
}


// 检查协议后缀
static bool check_suffix(const char* data, size_t len) {
    //省略处理代码
    return true;
}


bool check_protocol_xx(const char* data, size_t len) {
    return check_header(data, len) && check_body(data, len) && check_suffix(data, len);
}

上面的例子模拟了一个检查协议xx格式的库,对外暴露的是check_protocol_xx函数,其依赖的check_headercheck_bodycheck_suffix只是本编译单元使用,使用了static限定。当然,这套机制可以在C++中使用,但实际上C++还引入了另一套机制实现这类功能,主要原因在于C中的这套机制仅能用于函数和变量,对于C++中的类等自定义类型无能为力。所以在C++中实现这类功能首选使用下面要介绍的匿名名字空间。

使用匿名名字空间表达内部连接

首先对上面的例子使用匿名名字空间进行改写,从而直观感受匿名名字空间的使用方式:

// parse.h
#pragma once


//检查协议的有效性
bool check_protocol_xx(const char* data, size_t len);
// parse.cpp
#include "parse.h"


namespace {
  // 检查协议头
  bool check_header(const char* data, size_t len) {
      //省略处理代码
      return true;
  }


  // 检查协议包体
  bool check_body(const char* data, size_t len) {
      //省略处理代码
      return true;
  }


  // 检查协议后缀
  bool check_suffix(const char* data, size_t len) {
      //省略处理代码
      return true;
  }   
}


bool check_protocol_xx(const char* data, size_t len) {
    return check_header(data, len) && check_body(data, len) && check_suffix(data, len);
}

可以看到我们将之前的三个函数去掉static限定后放在namespace后的大括号内,转换过程比较直接。匿名名字空间跟普通的名字空间最大的区别就是namespace后没有名字,故匿名名字空间也叫作不具名名字空间。这个例子与之前C的例子是完全等价的,网上不少文章的观点与之有差异,文章后面专门说明。

那么,匿名名字空间有名字吗?实际上是有的,只不过这个名字用户不知道,是由编译器负责生成,在本编译单元内唯一。由于用户不知道该名字,所以能保证不会被其他编译单元显式引用。C++17 标准文档有段伪代码,形象地说明了编译器对匿名名字空间的处理,匿名名字空间的定义会被替换为下面的代码:

inline namespace unique { /* empty body */ }
using namespace unique ;
namespace unique { namespace-body }

我们之所以能使用匿名名字空间内的名字,是因为编译器会自动生成using的代码导出这些名字。

匿名名字空间用起来比较容易,有两个点需要特别说明下,作为本文的结束。

匿名名字空间定义放在哪合适

通常放在源代码里,放在头文件会有两个问题:

  • 与惯例约定相违

    头文件通常仅放对外导出的名字,由于匿名名字空间里名字是无法导出的,放在这里不合适。

  • 可能会引发名字冲突

    放在头文件就会被多个文件包含,存在一种情况:两个本来不同的匿名空间同时导入到了一个编译单元,而这两个匿名空间包含相同的名字,这样就会引起名字冲突。因为名字空间具有扩展性,编译器会认为导入到同一编译单元的匿名名字空间是同一个。

所以,匿名名字空间定义适合放在源代码。

匿名名字空间内的名字的连接性如何

这个问题涉及比较多的规则,我们简单地将问题落在上面例子上:check_header具有外部连接还是内部连接?

网上好多文章支持具有外部连接,这个观点是错误的,C++17标准明确规则了这类名字具有内部连接,引用的标准原文如下:

A name having namespace scope (3.3.6) has internal linkage if it is the name of— a variable, function or function template that is explicitly declared static; or,— a non-volatile variable that is explicitly declared const or constexpr and neither explicitly declared extern nor previously declared to have external linkage; or— a data member of an anonymous union.

An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage. A name having namespace scope that has not been given internal linkage above has the same linkage as the enclosing namespace if it is the name of— a variable; or— a function; or— a named class (Clause 9), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (7.1.3); or— a named enumeration (7.2), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (7.1.3); or— an enumerator belonging to an enumeration with linkage; or— a template.

标准的判断逻辑是这样的:匿名名字空间是内部连接的,由于check_header不满足第一段内部连接的判断条件,且由于该名字指代函数,所以连接性跟匿名名字空间保持一致,即内部连接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值