Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于const引用的问题 #83

Open
PoacherBro opened this issue Jun 14, 2018 · 12 comments
Open

关于const引用的问题 #83

PoacherBro opened this issue Jun 14, 2018 · 12 comments

Comments

@PoacherBro
Copy link

PoacherBro commented Jun 14, 2018

《C++ Primer》中文版第五版第 2.4.1 章里 const的引用,有几点疑惑:

  1. 下面代码会报错
int i = 42;
const int &r1 = i;
int &r4 = r1 * 2; // 错误,即使把 r1 替换成 i,也会报错

虽然后面解释了C++会创建一个常量类型的临时量,但是这里有一个计算r1 * 2,所以这里意思是如果表达式里有常量,会把字面量(这里是2)也设置为常量吗?

  1. 如上所描述,那么常量在赋值的时候也会和普通变量一样做一次拷贝吗?譬如
const int i = 32;
const int j = i; // 是否也有拷贝?拷贝的是地址还是32这个值对象?
  1. 看很多博客说常量引用不占用空间,那么引用是如何存储这个名字的?
@pezy
Copy link
Member

pezy commented Jun 14, 2018

  1. 引用就是"别名",r1 * 2 没有名字(后面你会知道,这是个右值),所以没法起别名,报错。
  2. 拷贝的是值。你可以试试,通过 const_cast改变 i 的值,j 的值不会变化。
  3. const 引用记的是地址,所以仅占用了指针大小的空间。

@PoacherBro
Copy link
Author

多谢大大。

  1. 既然右值没有办法起别名,那为什么const int &r4 = r1 * 2又可以呢?
  2. 引用记的是地址,那是不是可以理解引用是一种特殊的指针,只是C++内部已经帮我们做好了“地址引用”和“解引用”的操作?

@pezy
Copy link
Member

pezy commented Jun 14, 2018

  1. const 引用很强大,它既可以指向左值,也可以指向右值。
  2. 可以这样理解。

@LukaMod
Copy link

LukaMod commented Jun 14, 2018

菜鸡补充一点
原问题 1 中,字面值本身都是常量。

@PoacherBro
Copy link
Author

多谢两位解惑。

跳到书的13.6.1章节,看到右值引用描述,觉得可以用右值和左值来理解。
要求转换的表达式、字面量和返回右值的表达式 => 这是右值,其他都是左值。结合 @pezy 说的“ const引用既可以指向左值,又可以指向右值”,就好理解了。

还得继续学习拷贝、赋值、销毁和移动对象这些内容 :)。

@PoacherBro
Copy link
Author

PoacherBro commented Jun 14, 2018

@pezy 再次请教一下,关于函数返回引用的问题。

int& func()
{
    int i = 0;
    return i;
}

int main()
{
    int ri = func();
    std::cout << ri << std::endl;
}

我在VS2012-64位版本编译和运行没有问题,但是在G++-8.1.1编译,会报警告
image

image

这是为什么?函数fun()里面的i是局部变量,按理来说在函数调用完成后应该会被销毁,再引用的话是会报错的吧?迷惑了。

@pezy
Copy link
Member

pezy commented Jun 15, 2018

函数fun()里面的i是局部变量,按理来说在函数调用完成后应该会被销毁,再引用的话是会报错的吧?

说的没错啊,你运行一下试试看?肯定 segmentation fault 了吧?

另,VS2012 也太老了,好歹也用个 2015 啊。

@PoacherBro
Copy link
Author

PoacherBro commented Jun 15, 2018

编译和运行都没有问题。在配置里设置的编译警告是/W3,后来改成/W4,能看到警告,但是运行还是没有问题,正常。
在g++会报错。

在SO问了这个问题,根据大神的说法,了解了这个属于C++的Undefined behavior,不同的编译器可能处理不同。

@PoacherBro
Copy link
Author

另外可以看看之后有一个人问的SO - Passing non-lvalue as const reference argument. Is the temp created in local scope or caller scope?,关于const引用是否会延长临时量的声明周期。

总结来说,临时量的声明周期不会因为第二次引用而延长,后面对临时量的引用都可能导致dangling reference。

@ender233
Copy link

  1. 返回指向局部变量的引用,这本身就是未定义的. 编译器并不会做什么保证.
  2. 返回指向局部变量的常量引用,这个是c++的一个特性,能够延长位于栈上的局部变量的生存周期(延长至和当前常量引用的作用域)

可参考: https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/

Normally, a temporary object lasts only until the end of the full expression in which it appears. However, C++ deliberately specifies that binding a temporary object to a reference to const on the stack lengthens the lifetime of the temporary to the lifetime of the reference itself, and thus avoids what would otherwise be a common dangling-reference error. In the example above, the temporary returned by f() lives until the closing curly brace. (Note this only applies to stack-based references. It doesn’t work for references that are members of objects.)

(最佳)实践

一般不单独返回引用(大概率是用错了),而是和输入引用搭配用来级联调用,比如常见的输入输出流:

ostream & print(ostream &os, std::string message) {
    return os << message << std::endl;
}
// 调用
std::cout << "hello" << print(std::cout, "paul");

而对于const Type &作为返回值应用比较多一些,用起来来比Type &作为输入函数要简单点(少了一步变量声明和初始化动作)

@PoacherBro
Copy link
Author

PoacherBro commented Jun 15, 2018

@ender233 多谢分享。确实,对于引用来说多数是作用于函数形参和返回值。

对于引用延长临时生命周期,确实是会,不过有三种异常情况。
参考cpp官网cpp - Reference initialization里面关于Lifetime of a temporary的说明,对于函数返回的局部变量的引用是其中一种。接收的引用是不能延长它的生命周期的。

总结一下就是

In general, the lifetime of a temporary cannot be further extended by "passing it on": a second reference, initialized from the reference to which the temporary was bound, does not affect its lifetime.

你链接的那篇文章,里面的代码并不是返回一个引用,而是一个字面量。

@ender233
Copy link

你链接的那篇文章,里面的代码并不是返回一个引用,而是一个字面量

无论是字面量还是局部对象,都在栈上,都可以用const Type &绑定。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants