得之我幸 失之我命

when someone abandons you,it is him that gets loss because he lost someone who truly loves him but you just lost one who doesn’t love you.

Cpp11 auto

c++11 中的 auto 是一个占位符,编译器在编译期间自动推导出变量的类型,并适当地改变结果类型使其更符合初始化规则,因此 auto 定义的变量必须有初始值

1
2
int x;
auto y; // error

auto 碰上 const

顶层 const VS 底层 const:

  1. 被修饰的变量本身无法改变的 const 是顶层 const
  2. 通过指针或引用等间接途径来限制目标内容不可变的 const 是底层 const

有以下规律:

  1. 当 auto 带引用时,auto 的推导结果保留表达式的 const 属性
  2. 当 auto 不带引用时,auto 的推导结果忽略表达式的顶层 const 和引用的底层 const,保留指针的底层 const
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int i = 0;
const int ci = 1; // <=> int const ci = 42; const 修饰 ci,是顶层 const
const int& ccr = 2; // 底层 const
const int& cir = i; // 引用自身实现是顶层 const(int& cir <=> int* const cir),const 修饰 &cir,是底层 const
const int* cip = &i; // const 修饰 *cip,是底层 const
int* const ipc = &i; // const 修饰 ipc,是顶层 const
const int* const cipc = &i; // 靠前的 const 是底层 const,靠后的是顶层 const

auto& ar_i = i; // auto -> int
auto& ar_ccr = ccr; // auto -> const int
auto& ar_ci = ci; // auto -> const int
auto& ar_cir = cir; // auto -> const int
auto& ar_cip = cip; // auto -> const int*
auto& ar_ipc = ipc; // auto -> int* const
auto& ar_cipc = cipc; // auto - > const int* const

auto a_i = i; // auto -> int
auto a_ccr = ccr; // auto -> int
auto a_ci = ci; // auto -> int
auto a_cir = cir; // auto -> int
auto a_cip = cip; // auto -> const int*
auto a_ipc = ipc; // auto -> int*
auto a_cipc = cipc; // auto -> const int*

auto 碰上 for-range loop

for-range loop 是和 auto 一起出现在 c++11 中的,和 auto 搭配使用,非常提高生产力

先说结论:

  1. 当需要拷贝 range 的元素时,使用 for(auto x : range)
  2. 当需要修改 range 的元素时,使用 for(auto && x : range)
  3. 当需要只读 range 的元素时,使用 for(const auto & x : range)

for(auto x : range)

会为 range 的每一种元素都创建一份拷贝,不会改变 range 里面的元素

但是有两个特殊的情况

  1. 用于含有 proxy class reference 的 class 时,结果可能会出乎意料

    例:vector,它的[] 返回的不是 bool,是一个表示单独 bool 引用的 proxy class(平常使用 bool 没事,是因为完成了一个隐式转换),而用 auto 的话,想要得到意想中的类型,需要使用 static_cast,再给 auto

    1
    2
    3
    4
    5
    6
    7
    std::vector<bool> vec {false, false};
    for(auto x : vec) {
    x = true; // all elements be true,x 是一个 _Bit_reference proxy class
    }
    for(bool x : vec) {
    x = true; // won't change vec and work as you expected.
    }
  2. 不能用于含有 std::unique_ptr 等只有 move 语意的容器,因为 auto 在进行拷贝,而这类容器没有

    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::vector<std::unique_ptr<int>> vec;
    for(auto x : vec) {} // compile error
    /* error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]’
    21 | for(auto x : vec) {}
    | ^~~
    /usr/include/c++/9/bits/unique_ptr.h:414:7: note: declared here
    414 | unique_ptr(const unique_ptr&) = delete;
    | ^~~~~~~~~~
    */

for(auto& x : range)

当需要修改 range(不含有 proxy class reference 处理时)里面的元素时,使用auto&

而 auto& 的使用同样有一种特殊情况,即 range 中是含有 proxy class reference 的元素

例:用于 vector 时,会产生编译错误,因为 vector 返回的是 _Bit_reference proxy class 临时对象,而这个临时对象不能绑定在 non-const l-value reference 中

1
2
3
4
5
6
std::vector<bool> vec {false, false};
for(auto x : vec) {} // compile error
/* error: cannot bind non-const lvalue reference of type ‘std::_Bit_reference&’ to an rvalue of type ‘std::_Bit_iterator::reference’ {aka ‘std::_Bit_reference’}
20 | for(auto& x : vec) {}
| ^~~
*/

for(auto && x : range)

很好的综合了上述两种情况:

  1. 当被左值初始化时,auto&& 是左值引用(l-value reference)
  2. 当被右值初始化时,auto&& 是右值引用(r-value reference)

for(const auto& x : range)

当只需要读取 range 里面的元素时,使用 const auto&,这种用法没有特殊情况,即使是 std::vector、 std::vector<std::unique_ptr>,而且它也没有进行拷贝,所以大多数情况下,这会是你的首选

两种没有使用场景的组合

for(const auto x : range):使用 auto 代表需要拷贝元素,但是加上 const 来代表不要修改拷贝出来的元素,那么更好的使用是const auto&,因为这样可以避免拷贝开销,并且不会改变元素

for(const auto&& x : range):const auto && 将会只绑定右值,而且也无法进行修改元素,所以,更好的使用是const auto&,应用更广

be slow to promise and quick to perform.