Effective C++ 第二版 25)避免指针和数字重载 26)二义性

深入解析6T SRAM的读写机制与性能优化 本文深入解析了6T SRAM的基本结构与核心工作原理,详细阐述了其读写操作的具体时序与机制。文章重点探讨了在先进工艺下面临的读写稳定性、写入能力等性能挑战,并系统介绍了写入辅助与读取辅助等关键优化技术,旨在提升SRAM的可靠性与整体性能。 阅读详情

条款25 避免对指针和数字类型重载

什么是零?

1
2
3
void f(int x);
void f(string *ps);
f(0); // 调用f(int)还是f(string*)?

>0是一个int, 一个字面上的整数常量, 所以被f(int)调用;

Problem: 不是所有的人期望他这样执行; [有人希望是传个空指针NULL - #define成了0]

这是C++中特有的情况: 当人们认为某个调用应该具有多义性时, 编译器却不会那样做;


如果想办法用符号名(NULL表示null指针)来解决这类问题, 实现起来却很难;

声明一个称为NULL的常量, 常量要有类型, NULL的类型要兼容所有的指针类型, 满足条件的唯一类型是void*; 想要把void*指针传给某类型的指针, 必须要有一个显式的类型转换; 

1
2
3
4
void const NULL = 0; // 可能的NULL 定义
f(0); // 还是调用f(int)
f(static_cast<string*>(NULL)); // 调用f(string*)
f(static_cast<string*>(0)); // 调用f(string*)

>不好看也不方便; 但是用NULL来表示void*常量的方法比原来的好一点, 因为用NULL表示null指针可以避免歧义:

1
2
3
f(0); // 调用f(int)
f(NULL); // 错误! — 类型不匹配
f(static_cast<string*>(NULL)); // 正确, 调用f(string*)

>现在把运行时的错误(对0调用"错误的"f函数)变成了编译时的错误(void*传给string*参数);

即使使用预处理, 也解决不了问题:

1) #define NULL 0 或  2) #define NULL ((void*) 0)

1)是字面上的0, 本质上还是一个整数常量; 2) 又回到 传void*指针给某种类型的指针 的麻烦中;

C++认为 从long int 0到null指针的转换 和 从long int到int的转换 一样, 没有问题; 可以利用这一点, 将多义性引入到上面那个可能认为有"int/指针"问题的地方:

1
2
3
4
#define NULL 0L // NULL 现在是一个long int
void f(int x);
void f(string *p);
f(NULL); // 错误!——歧义

但是, 想重载long int和指针时, 他又有问题:

1
2
3
4
#define NULL 0L
void f(long int x); // 这个f 现在的参数为long
void f(string *p);
f(NULL); // 正确, 调用f(long int)

[NULL is supposed to be a point.]

实际编程中, 这样比起把NULL定义为int可能会安全些, 但他只是转移了问题, 并没有消除问题;

Solution: 使用C++语言的特性: 成员函数模板

成员函数模板是在类的内部为类生成的成员函数的模板, 简称成员模板;

对于NULL, 需要一个"对每个T类型, 运作起来都想static_cast<T*>(0)表达式"的对象; 使NULL成为一个"包含一个隐式类型转换符"的类的对象, 这个类型转换运算符可以适用于每种可能的指针类型; 

1
2
3
4
5
6
7
8
9
10
// 一个可以产生NULL 指针对象的类的第一步设计
class NullClass {
public:
    template<class T> operator T*() const return 0; } // 为所有类型的T产生operator T*;
}; // 每个函数返回一个null 指针
//...
const NullClass NULL; // NULL 是类型NullClass的一个对象
void f(int x); // 和以前一样
void f(string *p); // 同上
f(NULL); // 将NULL 转换为string*, 然后调用 f(string*)

[值为0, 但是类型为 T]

改进: 

1) 实际上只需要一个NullClass对象, 所以没必要给定一个类名字; 只需要定义一个匿名类, 并使NULL成为这种类型;

2) 既然是想让NULL可以转换成任何类型的指针, 那就要能够处理成员指针; 这就需要定义第二个成员模板, 他的作用是为所有的类C和所有的类型T, 将0转换为类型T C::*(指向类C里类型为T的成员, 条款30)

3) 要防止用户取NULL的地址, 希望NULL的行为并不是像指针那样, 而是像指针的值(e.g. 0x453AB002), 指针的值是没有地址的;

改进后的NULL:

1
2
3
4
5
6
7
8
9
10
const // 这是一个const 对象...
class {
public:
    template<class T> // 可以转换任何类型 的null 非成员指针
    operator T*() const return 0; }
    template<class C, class T> // 可以转换任何类型 的null 成员指针
    operator T C::*() const return 0; }
private:
    void operator&() const// 不能取其地址(见条款27)
} NULL; // 名字为NULL

>实际编程中可能会给类一个名字, 否则编译器里指向NULL类型的信息很难理解;

重要的一点是, 以上的产生的能正确工作的NULL设计方案, 只有在你自己是调用者的时候才有意义; 如果是一个给别人使用的NULL, 就没有多大用处, 因为你不能强迫别人使用你写的NULL;

用户还是会有这样的操作: 

1
f(0); // 还是调用f(int), 因为 0 还是int

Note 作为重载函数的设计者, 原则是只要可能, 就要避免对一个数字和一个指针类型的重载;


条款26 当心潜在的二义性

C++认为潜在的二义性不是错误;

e.g.

1
2
3
4
5
6
7
8
9
10
class B; // 对类B 提前声明
//
class A {
public:
    A(const B&); // 可以从B 构造而来的类A
};
class B {
public:
    operator A() const// 可以从A 转换而来的类B
};

>这些类的声明没错, 可以在相同的程序中共存;

但是当把两个类结合起来使用, 在一个输入参数为A的函数里实际传进一个B的对象:

1
2
3
void f(const A&);
B b;
f(b); // 错误!——二义

[不要写奇怪的代码...命名规则和namespace用起来]

>编译器对f的调用, 必须产生一个类型A的对象: 

1) 调用类A的构造函数, 以b为参数构造一个新的A对象; 2) 调用类B里自定义的转换运算符, 将b转换成一个A对象;  两种途径都可行, 编译器无法选择;

在没遇到二义性的情况下, 程序运行没问题, 这正是二义性具有潜伏的危害性; 他会潜伏在程序中, 一旦某天程序员真的做了具有二义性的操作, 混乱就会爆发; e.g. 你发布了一个函数库, 它可能在二义的情况下被调用, 你却不知道自己正在这么做;


另一种类似的二义形式源于C++语言的标准转换, 甚至没有涉及到类:

1
2
3
4
void f(int);
void f(char);
double d = 6.02;
f(d); // 错误!——二义

>d转换成int还是char都可行, 编译器无法选择;

Solution: 通过显式类型转换可以解决;

1
2
f(static_cast<int>(d)); // 正确, 调用f(int)
f(static_cast<char>(d)); // 正确, 调用f(char)

多继承(条款43)充满了潜在的二义性的可能; 最常常发生的情况是当一个派生类从多个基类继承了相同的成员名字时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Base1 {
public:
    int doIt();
};
class Base2 {
public:
    void doIt();
};
class Derived: public Base1, public Base2 {
// Derived 没有声明一个叫做doIt 的函数
};
//...
Derived d;
d.doIt(); // 错误!——二义

>当Derived继承两个具有相同名字的函数时, C++没有报错, 这时二义性只是潜在的; 当对doIt()的调用迫使编译器做出选择时, 除非显式地通过指明函数的基类来消除二义, 否则函数调用就会报错:

1
2
d.Base1::doIt(); // 正确, 调用Base1::doIt
d.Base2::doIt(); // 正确, 调用Base2::doIt

如果给以上的代码加上访问权限会怎样:

1
2
3
4
5
6
7
8
9
class Base1 { ... }; // 同上
class Base2 {
private:
    void doIt(); // 此函数现在为private
};
class Derived: public Base1, public Base2 { ... }; // 同上
//...
Derived d;
int i = d.doIt(); // 错误! — 还是二义!

Note 即使只有Base1中的函数可访问, 调用还是二义性的; 返回值不同也不会影响二义性;

如果想正确调用, 就必须指明类名 Class::func();

C++中有一些最初看起来很不直观[不符合直觉]的规定; 为什么消除"对类成员的引用所产生的二义"时不考虑访问权限?

有一个理由: 改变一个类成员的权限不应该改变程序的含义;

比如上例, 假设它考虑了访问权限, 于是表达式d.doIt()决定调用Base1::doIt, 因为Base2的版本不能访问; 假设Base1的doIt版本由public改为protected, Base2的版本由private改为public; 一下子, 同样的表达式d.doIt()将导致另一个完全不同的函数调用, 即使调用代码和被调用函数本身都没有被修改; 这很不直观, 编译器甚至无法产生警告; 可见, 对多继承的成员的使用要显式地消除二义性是有道理的;

既然写程序和库时会有多种不同的情况产生潜在的二义性, 那就应该: 时时小心; 想找出所有的潜在二义性的根源是几乎不可能的, 特别是当程序员将不同的独立开发的库结合起来使用时(条款28); 在了解导致经常产生二义性的那些情况后, 就可以字啊软件设计和开发中将二义性出现的可能性降低;

【虚幻引擎】UE4初学者系列教程基础篇-全中文新手入门教程(UE5系列继续更新) 菜单栏菜单栏当中有文件、编辑、窗口帮助。文件当中可以进行一个关卡的新建、关卡的打开、关卡的保存还有就是打包项目,(项目大于关卡,项目里面可能有几百个关卡),这是比较常用的几个,这里可以看到打包当中有安卓端HTML5端,IOS端linux,还有就是TVOS(苹果电视)、windows的一些端口的一些设置。还可以打开最近打开的项目。关卡编辑器:工具栏3.3 “模式”面板保存所有:ctrl+shift+s(有的软件截屏也是这个按键需要注意)3.4 “内容浏览器”面板缩放:ctr+鼠标滚轮3.5.视口面板。 阅读详情

相关推荐

Effective C++_第2_中文.pdf

Effective C++_第2_中文.pdf,完美高清非扫描电子,带完整详细目录标签

使用函数指针解决函数重载二义性调用问题

使用函数指针解决函数重载二义性调用问题 当实参对应重载函数的多个可行函数,且每个可行函数各自在一个实参上实现了更好的匹配时,编译器会因为程序具有二义性而报错。 例如: #include<iostream> using std::string; using std::cout; using std::cin; using std::endl; void ff(int, int) {//...

shadowgully的博客 713

误码率检测软件

用于按比特检测误码率的软件,具体可以检测出错误的位置

二义性C++消除二义性

1.二义性 二义性的定义是:“如果文法G中的某个句子存在不只一棵语法树,则称该句子是二义性的。如果文法含有二义性的句子,则称该文法是二义性的。”(该定义来自于百度百科)用通俗的话讲,如果一句话或者一个符号具有两种以上的解释含义,就说明该话或者符号具有二义性二义性意味着含义不清不明确。 在计算机语言中,如果语言具有二义性,必须消除二义性,才能使程序正常运行。 ...

wangzhezhilu001的专栏 7632

Effective C++ 第二

r00tk1t 二进制安全 | Web安全 | 伪装成程序员的菜鸡 博客首页 所有文章 标签 mailgithubzhihuweiboQQ SEH overwritealgorithmbrowserchromechrome-sandboxcppcpp-primercpp-templatesdll hookingexploitglibcglibc-heaplinuxlinux-...

chenxuanhanhao的专栏 653

Effective C++ 第二 1)constinline 2)iostream

从C转向C++ C提供了宏, 指针, 结构, 数组, 函数; C++还有私有保护型成员, 函数重载, 缺省参数, 构造析构, 自定义操作符, 内联函数, 引用, 友元, 模板, 异常, 名字空间等; C++具有更多的选择;

Fix it 1371

Effective C++

条款1:尽量用constinline而不用#define条款2:尽量用而不用条款3:尽量用newdelete而不用mallocfree条款4:尽量使用c++风格的注释条款5:对应的newdelete要采用相同的形式条款6:析构函数里对指针成员调用delete条款7:预先准备好内存不够的情况条款8:   写operator newoperator delete时要遵循常规条款9:   避免

梦开始的地方  1716

Effective C++(四)

条款25避免指针数字类型重载 因为0既可以是一个整型值,也可以是空指针。 条款26:当心潜在的二义性 二义性当然要避免,本条款强调的是“潜在”二字。很多时候,二义性会隐藏得很深,所以要尽可能地小心。 条款27:如果不想使用隐式生成的函数就要显示地禁止它 有些函数,当你没有声明定义它们时,编译器会自动为你生成,但它们是基于默认语义的,所以如果这不是你想要的,就需要显示

hh1562535601的专栏 423

Effective C++标题整理

Effective C++话说光看这50个tip又有什么用呢?只有实际使用的时候才体现它们的价值才对。就像只看《代码大全》不能成为一个好程序员,必须结合实际写项目经验才行。从C转向C++条款1:尽量使用constinline而不用#define条款2:尽量用而不用<stdio.h>条款3:尽量用newdelete而不用mollocfree内存管理条款4:尽量使用C++风...

diaomiezuo8055的博客 200

Effective C++简记

1、尽量用 const inline ,而不用 #define 2、尽量用 &lt;iostream&gt; 而不用 &lt;stdio.h&gt; 3、尽量用 new delete 而不用 malloc free 4、尽量使用 c++ 风格的注释 5、对应的 new delete 要采用相同的形式 6、析构函数里对指针成员调用 delete 7、预先准备好内存不够的情...

半雨微凉 364

effective c++条款总结

条款1:尽量用const inline 而不用 #define 条款2:尽量用 而不用 条款3:尽量用new/delete 而不用malloc/free 条款4:尽量使用c++风格注释 条款5:对应的newdelete 都要采用相同的形式 条款6:析构函数里对指针成员调用delete条款 条款7:预先准备好内存不够的情况 条款8:写operator new与operator d

qq_37185716的博客 475

Effective C++都有哪些条款

     其实很早以前就学了Effective C++,今天又看了下下,但是很难记住,或者说很容易忽略一些条款。so,我感觉有必要把条款在列出来一遍,呵呵,帮助自己记忆。当然,最重要的还是多多的实践,在项目中多多使用,多思考,相信自然在使用的时候就游刃有余了。 条款1:尽量用const inline 而不用#define条款2:尽量用而不用条款3:尽量用new delete 而不用ma

Laiger001的专栏 856

Effective_C++25避免指针型别数值型别进行重载

25避免指针型别数值型别进行重载 1、指针型别数值型别进行重载的问题 指针保存的是其所指对象的地址,解析时将其变为数值类型。尤其是有时我们把一个空指针定义为0,则当传入一个值为0的参数,若对指针型别数值型别都进行重载的函数,应当出现编译错误,因为此时编译器不知道0是指针型别还是数值型别。然而,实际上,编译器对于入参为0的情况,会认为其为数值型别,会调用参数为数值型别的函数,并不会出现...

l1dian11的博客 278

Effective C++中文 2nd Edition

下载 EffectiveC++ 2ed 中文目录(Contents) vii前言(Preface) xiii致谢(Acknowledgments. 中文略) xvii导读(Introduction) 001改变旧有的C习惯(Shifting from C to C++) 013条款1:尽量以 const inline 取代 #define 013Prefer const and inl

rabies的专栏 1148

避免指针数字类型重载以及潜在的二义性

指针数字类型都是数字避免重载两种类型,否则可能产生不可避免的麻烦。

一个菜鸟的博客 492
上一篇: Linux命令速查
下一篇: Effective C++ 第二版 27)函数隐式生成 28)名字空间
罗伊yc
博客等级 码龄22年 23粉丝 113原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值