`
阿尔萨斯
  • 浏览: 4195897 次
社区版块
存档分类
最新评论

Who is const ?!铪铪铪铪铪铪

 
阅读更多
<iframe align="center" marginwidth="0" marginheight="0" src="http://www.zealware.com/csdnblog.html" frameborder="0" width="728" scrolling="no" height="90"></iframe>

这篇文章主要是看了王咏刚先生的一篇“关于C++模板和重载的小问题”而发的,可以说是一篇回贴,之所以独立出来是因为这个问题“有趣”:)

先引用一下王咏刚先生的例子:
class Node
{
public:
int m;
Node(int value) : m(value) {}
friend ostream& operator};

ostream& operator{
o m return o;
}

int main()
{
Node* p = new Node(10);
cout

list l;
l.push_back(p);
copy(l.begin(), l.end(), ostream_iterator(cout, "/n"));
}
在王咏刚先生的编译器上,结果是10,0x23455322,也就是说,前者调用了用户重载的哪个operator 这个问题其实非常简单,可以简化如下(将Node类型替换为int相信大家不会反对吧):
void f(const int* & ); #1
void f(const void* ); #2
int main()
{
int* p=0;
f( p ); //选哪一个?#1? #2?
}
咏刚先生说选#1,因为#1更匹配int*,但事实恰恰相反,#1根本不能匹配!!也就是说,下面的代码从根本上就是错误的:
int* p;
const int* & rp=p; //错误!!!!!
关键在于,对于上面的例子,rp的类型“const int* &”到底是什么?这样才能确定int*能否向它转换。是“int*的const引用”吗?完全错误!应该是“non-const reference to 'const int*' ”!凭什么这样说呢?
这里关键的问题是,到底谁才是const的,即最前面的const到底修饰谁?”,根据C++98标准8.3.2对引用的定义:

对于声明:
T D;
如果D具有如下形式:
& D1 //注意,&与D1之间不能有任何cv修饰符
那么标识符D1的类型为“reference to T”。

这段标准很关键,我们来依葫芦画瓢,对于“const int* & rp;”这个怪胎和罪魁祸首,如果套用上面的格式,则:
T D;
const int* &rp;
这样,D就具有了&D1的形式,其中D1为rp。而T,则是const int*,这是个类型(废话:) ),其含义是“pointer to 'const int'”,因为解析指针的格式与解析引用的格式(上面已经列出)几乎相同,只不过将&换成了*(见C++98 8.3.2)。现在清楚了吗?

“const int* &”的含义是“a non-const reference to T where T is a pointer to 'const int' ”。

之所以用英文来描述是因为在这里变化多端语义微妙的中文实在容易误导。
现在,问题就在于,能否将“int*”转型为“a non-const reference to T where T is a pointer to 'const int' ”呢?你可能会立刻说:“咦?不是能吗?”,并给出转换路径:
int* ——> const int* ——> const int* &
你甚至给出了等价的例子:
int* p=0;
const int* cp=p;
const int* &rcp=cp;
更让你惊喜的是,这段例子竟然通过编译。是的,的确能,而且的确是对的。那问题出在那里呢?我想你忘了最基本的一条规则:不能将non-const的引用绑定到右值(rvalue)。在你给出的转换路径中,从int*转换到const int*创造了一个临时变量,临时变量是右值(rvalue),下面要将这个右值绑定到non-const引用却是万万不能了!至于你给出的例子能通过编译的原因是由于它用一个中间变量cp来承接了转换后的值,cp是个左值(lvalue),可以绑定到non-const引用。至于为什么要有这条古怪的规则是为了避免不可理解的语义,考虑下面的例子:
int i= 0;
double& rd=i; //错,左值不能绑定到non-const引用
rd=10;
这里,i转换为一个double临时变量,这个变量无法绑定到double&。所以是错误的。试想,如果允许这个例子通过编译,那么程序员会认为rd绑定到了i,从而“rd=10;”改变了i的值,事实恰恰相反,rd只不过绑定到了一个临时的double,改变的是个临时变量,i没有变。这就导致了语义模糊。而如果这里绑定到的是个const引用就不同了——“rd=10;”根本不能通过编译,也就杜绝了错误。

对于“const int* &”这个古怪的类型,一个常见的错误就是将int*放在一起而将const孤立出来分析,从而导致错误的理解为:“a const reference to 'int*' ”。C++标准和我们开了个不大不小的玩笑,这主要是由于加入了引用造成的,或者,干脆从语法上说,是加入了“&”符号造成的,另一个原因是继承了C里面的劣根性——“const int i”和“int const i”表示同一个意思“一个整型常量”。

[蛇足],其实“const引用”这个称谓不妥当,就连C++标准里都说“const reference”,其实如果要求和指针的说法一致,应该为“reference to const T”。之所以连C++标准都这样称呼可能是由于reference 其值本来就是const的,所以偷了个懒。而且reference一旦绑定导某个对象,说该reference其实就是再说那个对象,所以"const reference"就不难理解为“const的'那个对象'”了。而对于指针就不同了,“const int* p和int * const p”是完全不同的,前者是“pointer to const int”,后者是“const pointer to int”,大不相同。C++标准偷懒没有错,但是这种“const reference”的说法却是会误导人的。一般人喜欢将“&”符号读作“reference”,“const reference”就意味着const在前reference在后,而“const int* &”恰恰符合这个概念,所以很容易在一念之间就犯了错。C++标准的制定者也许没有想到一个简便称呼会带来这个隐晦的篓子吧。反之,如果强调“reference to const”,则程序员拿到这个类型就会先考虑“reference to what?”这个问题,从而得到正确的答案。所以,建议大家在阅读这种类型的时候,最好反过来读:先将“* cv-qualifier p”或“& ref”剥离开来,然后看剩下的是什么类型,这样就一清二楚了。另外,平时编码时勤用typedef,例如,上面的例子如果用typedef就不会出篓子了:
typedef int* int_ptr;
void f(const int_ptr& ); #1
void f(const void* ); #2
int main()
{
int_ptr p=0;
f( p ); //调用#1
}
因为int_ptr从语义上来说是个整体,是“一个”类型,所以按照你通常的理解方式,就不会出错了。但是还要注意,千万不能将typedef换成宏,宏的替换发生在所有的编译期动作(词法分析,语法分析,语义分析等)之前,是纯粹的文本替换。而typedef是有语义内涵的。用宏只会换汤不换药。

BTW.

咏刚先生用的编译期可能允许将临时变量绑定到non-const引用,这就是为什么在他的例子里面,第一次输出 “cout

至于咏刚先生所说的“模板参数推导改变了参数类型”则是没有的,原因如下:

Node * const & 其实可以看作是 Node* const,因为引用就代表着被引用的对象本身,至于为什么不能绑定到 const Node* &,我猜是由于其转换路径所致:

Node* const ——>const Node* ——> const Node* &

这里编译器的逻辑可能是“第一步转型把指针值本身的const修饰给去掉了,不行”。可惜事实并非如此,因为第一步转换只是产生一个临时变量,属于值拷贝,这种const丢失并没有错,例如:

const int & i=0;

int j=i; //ok




分享到:
评论

相关推荐

    巧用Delphi 2010 RTTI 管理常量(const)!

    巧用Delphi 2010 RTTI 管理常量(const)

    C++:关于const的思考.doc

    C++:关于const的思考.doc onst的思考 1、什么是const? 常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。(当然,我们可以偷梁换柱进行更新:) 2、为什么引入const?  const...

    C/C++:const常量与define宏定义的区别

     const常量是编译运行阶段使用。  (2) 类型和安全检查不同  define宏没有类型,不做任何类型检查,仅仅是展开。  const常量有具体的类型,在编译阶段会执行类型检查。  (3) 存储方式不同  define宏仅仅...

    strtok函数的使用示例

    is!the.matter;” 把这串字符串传入strtok函数,第二个delim写 “,:?!.;” , 这样就可以得到6个不同的子字符串。 我们来写个例子验证一下,就写分割时间的例子吧,获取UTC时间 如下: #include #include #...

    phosphore:快速,强大且免费的库来创建桌面应用程序

    入门 const { Window } = require ( 'phosphore' )// Create a new Windowvar window = new Window ( )window . display ( ) // Display the window... You can use .hide(), to hide the wind

    C++中const的实现细节介绍(C,C#同理)

    1、什么是const? 常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。(当然,我们可以偷梁换柱进行更新:)  2、为什么引入const? const 推出的初始目的,正是为了取代预编译...

    c++游戏程序员面试题

    .背景知识/名词解释: C++: ? static ? const ? volatile ? mutable ? namespace ? template ? explicit ? const_cast ? static_cast ? dynamic_cast ? reinterpret_cast

    总结C语言中const关键字的使用

    什么是const? 常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。(当然,我们可以偷梁换柱进行更新:) 为什么引入const? const 推出的初始目的,正是为了取代预编译指令,...

    logicgoose:库通过Db2 for i调用程序

    const inputDS = new rpgleDS ( ... ) ; const responseDS = new rpgleDS ( ... ) ; static async getHistoryHeader ( header ) { console . log ( inputDS . getSize ( ) ) ; console . log ( responseDS . get...

    Const,Const函数,Const变量,函数后面的Const.txt

    Const,Const函数,Const变量,函数后面的Const Const,Const函数,Const变量,函数后面的Const

    JavaScript前端开发时数值运算的小技巧

    const ThousandNum = num =&gt; num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); const money = ThousandNum(20190214); // money =&gt; "20,190,214" 2.取整代替正数的Math.floor(),代替负数的Math.ceil() ...

    bitmovin-api-sdk-[removed]TypeScriptJavaScript API SDK,使您能够将Bitmovin API无缝集成到项目中

    TypeScript / JavaScript API...const bitmovinApi = new BitmovinApi ( { apiKey : '&lt;YOUR&gt;' } ) ; 与require exports . __esModule = true ; const BitmovinApi = require ( '@bitmovin/api-sdk' ) [ "default" ] ;

    rds-tools:用于RDS的CDK构造

    const myUserSecret = new rds . DatabaseSecret ( this , 'MyUserSecret' , { username : 'myuser' , masterSecret : instance . secret , excludeCharacters : '{}[]()\'"/\\' , // defaults to the set " %+~...

    嵌入式开发面试题

    嵌入式开发面试题 ... 5、 哪些地方可以用到const?const变量和函数输入参数用const修饰有哪些作用?

    const的用法

    和大家一起分享下CONST的用法!!!

    Redux最简单的动作创建者-React开发

    const CONSTANT = action-helper redux的最简单的动作创建者。 安装npm install --save action-helper为什么? 减少代码生成。 每次写所有这些动作我都感到非常疲倦。 const CONSTANT ='CONSTANT'; // 为什么?! ...

    C++ 中const对象与const成员函数的实例详解

    C++ 中const对象与const成员函数的实例详解 const对象只能调用const成员函数: #include using namespace std; class A { public: void fun()const { cout&lt;&lt;const 成员函数!&lt;&lt;endl; } void fun()...

    const T vs. T const

    Dan_Saks 关于C语言中有关const的含义和用法剖析的英文原版文章

    const用法 const int *a; int * const a;

    const char* pCh; // 指向字符串常量的指针; 表示pch是一个指针,指向字符串常量。根据需要还可以指向另一个字符串。 但字符串不能被改变。 char* const pCh; // 指向字符串的常量指针; 表示pch是一个字符串常...

    game-of-life-rust:锈蚀二维环形网格上的康威生命游戏

    您可以更改const声明以调整网格的大小。 作者:Lyall Jonathan Di Trapani 测验 cargo test 箱 要发布新版本: cargo publish 测试覆盖率 sudo apt-get install pkg-config cmake zlib1g-dev RUSTFLAGS="--cfg ...

Global site tag (gtag.js) - Google Analytics