C++中,C::C::C::C::foo() 为什么编译成功?

news2024/11/25 0:48:31

有人问:

class Entity
{
public:
    static void foo() {}
};

int main()
{
   Entity::Entity::Entity::Entity::Entity::foo();
}

为什么 最后那行:

Entity::Entity::Entity::Entity::Entity::foo();

能编译成功?这是什么规则?

嗯……

Entity::Entity::Entity::Entity::Entity::Entity::foo() 竟然编译成功?这一切的背后,是人性的扭曲,还是道德的沦丧? 敬请关注今晚八点 CPPTV 12 频道,让我们跟随镜头走进厚厚的C++标准文档……

这个案例,至少牵涉到 C++ 中的 以下 知识点:

  1. “Unqualified name lookup / 未限定的名字查找”
  2. “Qualified name lookup / 有限定的名字查找”
  3. “Injected-class-name/ ‘注入式’类名称 ”以及 特别关注其中的 “Injected-class-name and constructors / ‘注入式’类名称 和 构造函数 的关系 ”。
  4. “ Elaborated type specifiers / 详细的类型说明符 ”
如果被限定的名称,最终发现是一个C++基础类型 (bool, int, char 等),那还得牵涉出: "pseudo-constructor-name 或 pseudo-destructor-name / 伪构造或伪析构名字 ” 等等

首先我们从 “Qualified name lookup ” 的一特例说起: A::A 表示什么?

如果 A 是一个类 (或结构,或相应的别名,以下均只以“类”代表 ),并且在上下文限定中,查找 A 符号的过程无须过滤掉函数名称, 那么, A::A 就只能表示 A 的构造函数的名字。比如:

struct A {};

using T = A::A;  // 编译失败
int main() {}

问: using 这行代码能编译通过吗?

答:不行的,编译将得到类似 “A::A 是构造函数,不是类型”这样的出错信息。如下图:

这是一个特例,如果 A 有一个基类叫 B,则 A::B 立刻表示 一个类型(即 B ),如:

struct B {};
struct A : B {};

using T = A::B; // 编译成功

int main() {}

那要怎么让 A::A 请示 struct A 这个类型呢?这就要用上 “Elaborated type specifiers”。方法是加上 typename 或 class 或 struct 之一。只要加上三者之一即可,并不需要和 A 倒底是 struct 还是 class 对应上。因为加上这三者,就是为了很 “elaborated ”地表示:这是一个类型。

我们就选 “typename”,因为它看起来如此直观:“类型名”:

struct A {};
using T = typename A::A;  // 编译成功
int main() {} 

这时候生效的是C++的哪一条规则呢? 答,“Qualified name lookup / 有限定的名字查找” 。先从操作符 “:: ”说起。

“ :: ”被称为 “ scope resolution operator ” 用于限定一个符号的查找范围,它的右边的符号,直观上,我们会叫它 是“查找目标”,它的左边,直观上会叫它 “查找范围”。比如 C::F ,很容易推想:编译器 的查找过程 就是 在 C 的有效范围里,查找 符号 F 是什么东东。如果是C是一个class/struct,那查找范围还可扩大到 它的基类(如果有)。事实上,C++标准也确实规定了:解析 C::F 时,编译器必须先解析 C ,再解析 F……然而,C++标准又规定了,在解析 C::F 中的C时,应该加某种优先级,跳过 “Unqualified name lookup”,以尝试将 C 解析为 某个类名 (class\struct\union) 、namespace 或 枚举名 (本质也是类型名)。比如,下面的代码肯定编译失败:

struct a 
{
   static int i;
};

int a::i;

int main()
{
    int a;
    a a1; // 编译失败。
}

“a a1;” 这行中的 a ,没有加任何空间限定,所以在查找 它 是什么时,用的是 “Unqualified name lookup / 未限定的名字查找”,其方法就是就近往前找,于是找到 a 是一个 int 变量(而不是一个类型),自然, “a a1;” 语法错误。

怎么让编译器知道我们希望写在这里的 a 是一个 类(或结构)呢?直观的想法当然是它加上限定:

struct a 
{
   static int i;
};

int a::i;

int main()
{
    int a;
    a::i = 666; // 编译成功
}

编译器解析过程如下:看到 “a::i”中有个 “::”,于是采用 “Qualified name lookup ”,于是此处的 a 不受上面 的 “int a”影响,优先 将它当成 类类型、namespace、或enum 查找……于是找到 struct a 。而 struct a 里面正好有个静态成员 i,满足 “a::i = 666”的操作……

注意,这个名字查找过程中,“a::i”基本被视为一个整体。否则,如果按要求,一定要先解析出 :: 的左边的 a 是什么的话,那由于 它本身 未再有 新的限定 (它的左边不再有 :: ),那么,它就应该被解析为 一个 整数变量;然后,整数变量后面接 “::i”,显然是错误的语义,编译失败——但实际情况是,编译成功了。因为,加了“::”后,“Qualified name lookup ”的优先级高于“Unqualified name lookup”了。

不过,这个“优先级”是有限的。如果两条或更多 “Qualified name lookup”的限定规则时,此时大家都是“有身份/Qualified”的人,谁也不比谁优先,于是编译器就只能报错了,比如:

namespace n1 {
struct a 
{
   static int i;
};

int a::i;
} // namespace n1

int main()
{
    int a;
    a::i = 666; // 编译失败,正确做法: n1::a::i = 666; 或 上面 加 using namespace n1;
}

a::i 仍然使用带限定的名字查找法,仍然优先于 int a 中的 “a”的作用;但它却找不到合适的 a了:现在 struct a 位于 另一个“qualified / 有限制的” 的空间范围内: n1 。此时,要么 加上 using namespace n1 ,要么明确使用 n1::a::i 。

对于“Qualified name lookup”, 我们还有个补充:在 一个类(假设类名为 C)的范围里写代码,此时对符号的查找,哪怕不加 “ C::”限定,也是会在 “Unqualified name lookup” 失败之后,主动加上“C::”作为 “Qualified”,再找一次的。

距离扣题,还有最后那么几步……上面我们讲了规则是什么什么,但没有讲为什么有这些规则;所以我们还需要一些规则必要性解释及“有某规则和没有某规则”的对比:

很早很早以前,那时的C++的class内,是不会自动采用 “Qualified name lookup”再查找一次的,所以:

/* 曾经,约30多年前的C++, 这个类定义会编译失败 */
class Coo
{
    char c;
public:
    void M()
    {
        c = 'A'; // 编译成功, 往前找 c ,发现它是 char
        a = 1;   // 编译失败,a 是什么?
        m();     // 编译失败, m 是什么?
        T t; // 编译失败,T 是什么?
    }   
private:
    typedef int T;
    void m();
    int a;
};

「纯猜测」 C++之父写的示例代码,到现在也常常将 私有成员 放在最前面,我怀疑他并不是为了省写一次“private”,我怀疑他就是习惯了之前的查找法。

注意上面的表达,当没有明确写 “::”时,在类中也仍然优先使用“Unqualified name lookup”,所以这才有C++程序员都熟悉的,非常“经典”的某种写法:

class Coo
{
public:
    Coo(int a, int b) : a(a), b(b) {}
private:
    int a, b;
};

以其中的 “a(a)”为例,表意是 用括号中(右边)的 a 初始化 括号外(左边)的 a 。两个 a 都不带 “::” 限定,因此都优先使用 “Unqualified name lookup”,而后 括号中的 a 解析成功,括号外的 a ,因为要作为初始化的目标,所以不可能构造函数的入参中的 a ,于是改用 “Coo::a ”进行“Qualified name lookup”,这回成功了。


现在来看问题中的 Entity :

class Entity
{
public:
     static void foo();
}

首先,如何按 “特例” A::A ,如果A是一个class/struct/,则A::A 必然用于表示 类A 的构造函数名字这规定,那么题目中的这个写法:

Entity::Entity::Entity::foo();

感觉是不合语法的。因为一开始的 “Entity::Entity” 就应该得到一个 构造函数的名字,而构造函数名字后面再接“::Entity::foo() ……” 是不合语法的。注意,如果没有的最后的 ::foo(),两个或更多的 Entity:: 相连,仍然在表达 一个构造函数。但在语法上,构造函数被不允许被直接调用(析构函数倒是可以),因此,能正确使用 A::A::A::A::A 这样的写法,基本就是在构造函数的定义\实现的时候了,比如:

// class Entity 的构造函数实现 (能通过编译):
Entity::Entity::Entity::Entity::Entity::Entity::Entity()
{
}

在别的地方这么写,编译器仍然会识别出这是一个构造函数,但它会基于其它规则而报错,比如:

void foo()
{
    // 直接调用 (但可惜构造函数不能直接调用)
    Entity::Entity();   // 报错:哎呀,不能直接调用 构造函数
}

再如前面使用过的例子:

// 尝试取类型别名 (但人家 Entify 此处不是类型名 )
using T = Entity::Entity::Entity; // 报错:哎呀,Entity 是构造函数的名字,不是类型啦

既然一串的 “Entity::Entity”表示的是 构造函数的名字,那么怎么解释 “Entity::Entity::Entity::foo();”却通过了编译,并且在运行期正确地执行了静态成员函数 foo() 呢?

首先,我们要证明一下,“XXX::Foo”作为静态成员函数的调用的一方式,前面加的XXX一定是一个类名,而不是构造函数的名字。

其实不证明也可以,因为C++标准规范中讲解 static member function 的调用时,明确就说那个 XXX 是 类名。但,证明一下也不难——

using T = typename Entity::Entity::Entity; //编译通过,T 现在就是类名

加了 typename (class, struct)之后,后面的 Entity::Entity::Entity ……就是“ Elaborated type specifiers / 详细的类型说明符 ”,于是它肯定是个类型名 (type specifiers),于是 T现在肯定就是一个类名,事实上就是 class Entity。

然后我们假借 T 来调用 静态成员:

T::foo(); // 成功

由此可证:foo() 前面 的“T::”,就是一个“类型限定”(而不是我们意想天开的构造函数名字)。

有意思(其实超级烦人)的事来了:既然 T 就是 typename Entity::Entity::Entity,而 T::foo(),又能成功编译、运行;那我们为什么一定要取个别名呢?直接这样写不行吗:

typename Entity::Entity::Entity::foo();  // 行吗?不行!

这样写多直观啊!可惜,替换率竟然在此失效了。这样写编译失败。因为 typename 直接修饰到了 foo(),而 foo() 是函数调用,显然不是一个(位于Entity类内的)类型名称。

那么我们加上括号,强行改变结合率:

(typename Entity::Entity::Entity)::foo(); // 行吗?也不行!

也不行,因为 typename 不是一个操作符,没有优先级这一说。 实际上,编译器(g++/clang)看到 typename 前面有个 左括号,就直接报错了。

显然,我们按照所谓“A::A”的特例,来解释 “Entity::Entity::Entity::foo()”的合法性,是走不通的。并且还不能怪C++标准,只能怪我们自己,因为人家标准说很清楚,是 A::A ,或都 A::A::A,而不我们要解释的,其实是 A::A::F 。最后的符号 是F而不是A,不满足特例。

Entity::Entity::Entity::Entity::Entity::Entity::foo() 竟然编译成功?这一切的背后,原来既不是人性的扭曲,更不是道德的沦丧,而是我们眼花看错了,原来 Entity::Entity::foo 并不符合 C::C 的特例,是我们自己想多而已。

真是豁然开朗啊!原来这就是一个普普通通的 “Qualified name lookup”嘛!就是 C::F嘛!只不过是 C写了好多几次,变成: C::C::C::F 而已嘛!结合本例,把 C 用 Entity 代入,把 F 用Entity 的静态成员调用 foo() 代入,得到:

Entity::Entity::Entity::foo();

按照 “Qualified name lookup” 规则,:: 左边的 Entity 应优先按 类名查找,于是找到 class Entity,并且它里面还正好有 foo 成员,并且还正好是 一个静态成员,可以直接通过 类名来调用,这就是: Entity::foo()。

切慢,前面还是有一大串 Entity::Entity:: 怎么解析或解释?也好办, 既然已经 是 C::F 形式,而不是特例 “C::C”形式,于是有关 Entity::Entity 是一个构造函数名字的选项,就已经失效,此时统一走 “Qualified name lookup”, 再结合 “Injected-class-name” 规则, Entity::Entity 就是得到类名 “Entity”,于是再多层 “Entity::Entity::Entity::Entity”,两两结合后,最终得到仍然是 一个类名:“ Entity”。而 类名 + :: + 静态成员函数,比如: “Entity::foo()”,不就是一次再普通不过的静态成员函数的调用吗?


夜色已深,古老的C++部落再次恢复它的安宁。

各位不要打我,其实我还是讲了很多C++方面的科学知识的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/725141.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何优雅的跳出 for 循环

文章目录 需求分析1. 普通for循环2. for..in循环3. for..of循环(ES6)4. forEach(callbackFn, ?thisArg)方法(ES5.1) 源码1. 终止 普通 for 循环2. 终止 forEach2.1 forEach 可以跳出本次循环,执行下一次循环2.2 forEach终止循环 需求 如何做到优雅的跳出 for 循环 …

像考研一样学个宇宙之刷题篇:剑指offerⅡ:整数系列——整数除法0706 TODO

001. 整数除法: 给定两个整数 a 和 b ,求它们的除法的商 a/b ,要求不得使用乘号 ‘*’、除号 ‘/’ 以及求余符号 ‘%’ 。 一些知识点和思路 第一题,easy题,狠狠来了个下马威。 首先是 “被除数/除数”关于溢出的情…

| 交互式建模与学习:重建人类运动功能

在报告《交互式建模与学习:重建人类运动功能》中,清华大学副教授眭亚楠介绍了AI在重建人类运动功能时,从无模型学习(model-free learning)到基于模型学习(model-based learning)的转变&#xff…

剑指 Offer II . 删除链表的倒数第 n 个结点

给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 示例 1: 输入:head [1,2,3,4,5], n 2 输出:[1,2,3,5] 示例 2: 输入:head [1], n 1 输出:[] 示例 3&#x…

【MySQL入门实战5】-Linux PRM 包安装MySQL

📢📢📢📣📣📣 哈喽!大家好,我是【IT邦德】,江湖人称jeames007,10余年DBA工作经验 一位上进心十足的【大数据领域博主】!😜&#x1f61…

【QT】QCustomPlot开发笔记

QCustomPlot开发 01、QCustomPlot简介1.1 帮助文档1.2 下载&使用 02、QCustomPlot项目使用笔记2.1 创建QCustomPlot 03、源代码 01、QCustomPlot简介 QCustomPlot 是一个用于科学绘图的 QT 第三方库,可以用于常见的二维图像绘制,比如函数曲线、参数…

管理类联考——择校——学费

截止2023年06月,关于广东的管理类联考的xuefei。 借鉴:https://zhuanlan.zhihu.com/p/421296334。罗列985相关院校 广州以本土MBA为主,共有9家院校; 深圳以外地MBA为主,有超过20家院校。 一梯队:北大光华…

日志---spdlog

spdlog中各对象都分为多线程与单线程版本: *_st:单线程版本,不用加锁,效率更高。*_mt:多线程版本,用于多线程程序是线程安全的。 spdlog是基于C11实现的一款纯头文件的日志管理库 git地址:htt…

WSI-finetuning

一、贡献 (1)通过引入一个IB模块来提出WSI-MIL的简单代理任务,该模块将包中超过10k个冗余实例提炼成不到1k个最受支持的实例。因此,在千兆像素图像上进行基于梯度的训练的并行计算成本减轻了十倍以上。通过对简化袋的学习和分类,发现由于病理…

【自学笔记】在SQL Server中创建用户角色及授权(使用SQL语句)更新2023.07.06

--<在SQL Server中创建用户角色及授权(使用SQL语句)>更新2023.07.06 --1. 首先在 SQL Server 服务器级别&#xff0c;创建登陆帐户&#xff08;create login&#xff09; --2. 创建数据库用户&#xff08;create user&#xff09;&#xff1a; --3. 通过加入数据库角色&a…

不忘初心,筑梦未来 | 【2023 ACDU 中国行·深圳站】数据库主题交流活动成功举办!

6月30日下午&#xff0c;【ACDU 中国行深圳站】在深圳回酒店圆满落下帷幕。本次活动由中国数据库联盟&#xff08;ACDU&#xff09;联合墨天轮社区主办&#xff0c;围绕「数据库前沿技术揭秘及应用」这一主题&#xff0c;七位数据库行业的领军人物从数据库新特性解读、创新与应…

Docker集群部署-redis集群

学习要求 利用Docker实现redis 集群的部署&#xff0c;实现3主3从集群配置&#xff0c;并在此基础上实现主从扩容、缩容。 学习准备 要求实验主机能够连接外网&#xff0c;已经正确安装Docker&#xff0c;并关闭防火墙和selinux。 学习步骤 创建6个docker容器实例&#xf…

OpenFeign 源码分析

&#xff08;学习别人的思想&#xff0c;可以找 bug&#xff0c;优化你的代码&#xff0c;提高代码的健壮性&#xff09;看源码之前要先大致猜想一下 他是怎么实现的&#xff1f;&#xff08;先使用在分析&#xff09; 5.1 OpenFeign 的原理是什么&#xff1f; 根据前文的案例…

3DCAT实时云渲染助力VR虚拟现实迈向成熟

近年来&#xff0c;虚拟现实&#xff08;Virtual Reality, VR&#xff09;技术在市场上的应用越来越广泛&#xff0c;虚拟现实已成为一个热门的科技话题。相关数据显示&#xff0c;2019年至2021年&#xff0c;我国虚拟现实市场规模不断扩大&#xff0c;从2019年的282.8亿元增长…

uniapp开发的APP升级、整包更新和热更新组件

插件地址&#xff1a;app升级、整包更新和热更新组件 仔细阅读说明文档&#xff0c;后台接口返回的数据格式要严格按照文档要求的格式返回&#xff0c;前端示例代码 或者根据实际业务修改 如果需要自动检测新版本&#xff0c;建议写在App.vue的onShow中&#xff0c; <scrip…

化繁为简——论五大市场风格

A股市场至今已有逾5000家上市公司&#xff0c;行业分析有助于化简选股过程&#xff0c;然而如果想要对于各个行业都获得高于平均水平的了解&#xff0c;行业分类体系又显得繁杂。以中信行业分类体系为例&#xff0c;其一级行业包括30个行业类别&#xff0c;二级行业包括109个行…

高效工作——PPT动画制作【图文板(1)】

今天&#xff0c;我来教大家如何制作PPT或PPTX动画。希望这对你能有所帮助。{提示&#xff1a;改变原文&#xff1a;查看本人的“高效工作——PPT动画制作【文字板&#xff08;1&#xff09;】”} 首先&#xff0c;打开WPS office&#xff0c;点击创建PPT&#xff0c;点击创建空…

安全头响应头(二)​X-Frame-Options​

一 X-Frame-Options 1) CSP 安全头与前端编程息息相关,后续通过对CSP头的理解加深对前端知识的理解 ① 点击劫持 说明&#xff1a;X-FRAME-OPTIONS是微软提出的一个http头,专门用来防御利用iframe嵌套的点击劫持攻击 相关参考 ② 简介 背景&#xff1a; 出于安全考虑…

5.8.8 TCP流量控制

5.8.8 TCP流量控制 计算机网络的流量控制实际上是调节发送方的速率使得接收方能够及时处理的一个过程。 在TCP中采用的是大小可变的滑动窗口的方式进行流量控制&#xff0c;窗口大小的单位是字节。 如图 根据接收方的接收能力&#xff0c;通过接收窗口rwnd可以实现一个端到端…

PMO对企业的价值:有效赋能+战略落地︱富途网络PMO总监苗秀娟

富途网络科技&#xff08;深圳&#xff09;有限公司PMO总监苗秀娟女士受邀为由PMO评论主办的2023第十二届中国PMO大会演讲嘉宾&#xff0c;演讲议题&#xff1a;PMO对企业的价值&#xff1a;有效赋能战略落地。大会将于8月12-13日在北京举办&#xff0c;敬请关注&#xff01; 议…