zoukankan      html  css  js  c++  java
  • C++ 常量引用与临时变量

    总结: 1.不要对临时变量进行改变。要传递临时变量,得用常量引用。

        2. 当引用不需要更改时,用const引用。

    问题:

    struct Sales_data {
        Sales_data() = default;
        Sales_data(std::string &str){}
        Sales_data& combine( Sales_data&);
    };
    
    Sales_data& Sales_data::combine( Sales_data& rhs) { // 使用普通引用作为参数
        return *this;
    }
    int main()
    {
        Sales_data i;
        std::string str("a");
        //string类型隐式变为Sales_data类型(临时变量)
        i.combine(str);//编译器报错,非常量限定
        return 0;
    }

    解决:

      参数使用const 引用

    复制代码
    struct Sales_data {
        Sales_data() = default;
        Sales_data(const std::string &str){}
        Sales_data& combine(const Sales_data&);
    };
    
    Sales_data& Sales_data::combine(const Sales_data& rhs) { // 使用常量引用作为参数
        return *this;
    }
    
    int main()
    {
        Sales_data i;
        std::string str("a");
        i.combine(str);
        return 0;
    }

    C++11: 引入rvalue, lvalue和move

    那么这个临时变量, 在以前是解决不了了. 为了填这个坑, 蛋疼的C++委员会就说, 不如把C++搞得更复杂一些吧!

    于是就引入了rvalue和lvalue的概念, 之前说的那些临时变量就是rvalue. 上面说的避免copy的操作就是std::move

    再回到我们的例子:

    没法避免copy操作的时候, 还是要用const T &把变量传进set函数里, 现在T &叫lvalue reference(左值引用)了, 如下:

    void set(const string & var1, const string & var2){
      m_var1 = var1;  //copy
      m_var2 = var2;  //copy
    }
    A a1;
    string var1("string1");
    string var2("string2");
    a1.set(var1, var2); // OK to copy

    传临时变量的时候, 可以传T &&, 叫rvalue reference(右值引用), 它能接收rvalue(临时变量), 之后再调用std::move就避免copy了.

    void set(string && var1, string && var2){
      //avoid unnecessary copy!
      m_var1 = std::move(var1);  
      m_var2 = std::move(var2);
    }
    A a1;
    //temporary, move! no copy!
    a1.set("temporary str1","temporary str2");
    using namespace std;
    void set(string && var1, string && var2)
    {
              //avoid unnecessary copy!
         //m_var1 = std::move(var1);  
         //m_var2 = std::move(var2);
    }
    void set2(string & var1, string & var2)
    {
              //avoid unnecessary copy!
         //m_var1 = std::move(var1);  
         //m_var2 = std::move(var2);
    }
    
    int main()
    {
            set("temporary str1","temporary str2");
            set2("temporary str1","temporary str2");
            return 0;
    }
    root@ubuntu:~/c++# g++ -std=c++11  rvalue.cpp -o rvalue
    rvalue.cpp: In function ‘int main()’:
    rvalue.cpp:20:40: error: invalid initialization of non-const reference of type ‘std::__cxx11::string& {aka std::__cxx11::basic_string<char>&}’ from an rvalue of type ‘std::__cxx11::string {aka std::__cxx11::basic_string<char>}’
      set2("temporary str1","temporary str2");
                                            ^
    In file included from /usr/include/c++/5/string:52:0,
                     from /usr/include/c++/5/bits/locale_classes.h:40,
                     from /usr/include/c++/5/bits/ios_base.h:41,
                     from /usr/include/c++/5/ios:42,
                     from /usr/include/c++/5/ostream:38,
                     from /usr/include/c++/5/iostream:39,
                     from rvalue.cpp:1:
    /usr/include/c++/5/bits/basic_string.h:455:7: note:   after user-defined conversion: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
           basic_string(const _CharT* __s, const _Alloc& __a = _Alloc())
           ^
    rvalue.cpp:10:6: note:   initializing argument 1 of ‘void set2(std::__cxx11::string&, std::__cxx11::string&)’
     void set2(string & var1, string & var2)
          ^
    #include<iostream>
    #include<string>
    using namespace std;
    void set(string && var1, string && var2)
    {
              //avoid unnecessary copy!
         //m_var1 = std::move(var1);  
         //m_var2 = std::move(var2);
    }
    void set2(string & var1, string & var2)
    {
              //avoid unnecessary copy!
         //m_var1 = std::move(var1);  
         //m_var2 = std::move(var2);
    }
    void set3(const string & var1, const string & var2)
    {
              //avoid unnecessary copy!
         //m_var1 = std::move(var1);  
         //m_var2 = std::move(var2);
    }
    
    int main()
    {
            set("temporary str1","temporary str2");
            set2("temporary str1","temporary str2");
            set3("temporary str1","temporary str2");
            return 0;
    }
    root@ubuntu:~/c++# g++ -std=c++11  rvalue.cpp -o rvalue
    rvalue.cpp: In function ‘int main()’:
    rvalue.cpp:26:40: error: invalid initialization of non-const reference of type ‘std::__cxx11::string& {aka std::__cxx11::basic_string<char>&}’ from an rvalue of type ‘std::__cxx11::string {aka std::__cxx11::basic_string<char>}’
      set2("temporary str1","temporary str2");
                                            ^
    In file included from /usr/include/c++/5/string:52:0,
                     from /usr/include/c++/5/bits/locale_classes.h:40,
                     from /usr/include/c++/5/bits/ios_base.h:41,
                     from /usr/include/c++/5/ios:42,
                     from /usr/include/c++/5/ostream:38,
                     from /usr/include/c++/5/iostream:39,
                     from rvalue.cpp:1:
    /usr/include/c++/5/bits/basic_string.h:455:7: note:   after user-defined conversion: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
           basic_string(const _CharT* __s, const _Alloc& __a = _Alloc())
           ^
    rvalue.cpp:10:6: note:   initializing argument 1 of ‘void set2(std::__cxx11::string&, std::__cxx11::string&)’
     void set2(string & var1, string & var2)

    新的问题: 避免重复

    现在终于能处理临时变量了, 但如果按上面那样写, 处理临时变量用右值引用string &&, 处理普通变量用const引用const string &...

    这代码量有点大呀? 每次都至少要写两遍, overload一个新的method吗?

    回忆一下程序员的核心价值观是什么? 避免重复!

    perfect forward (完美转发)

    上面说的各种情况, 包括传const T &T &&, 都可以由以下操作代替:

    template<typename T1, typename T2>
    void set(T1 && var1, T2 && var2){
      m_var1 = std::forward<T1>(var1);
      m_var2 = std::forward<T2>(var2);
    }
    
    //when var1 is an rvalue, std::forward<T1> equals to static_cast<[const] T1 &&>(var1)
    //when var1 is an lvalue, std::forward<T1> equals to static_cast<[const] T1 &>(var1)

    forward能转发下面所有的情况:

    [const] T &[&]

    也就是:

    const T &
    T &
    const T &&
    T &&

    那么forward就是上面一系列操作的集大成者.

    如果外面传来了rvalue临时变量, 它就转发rvalue并且启用move语义.

    如果外面传来了lvalue, 它就转发lvalue并且启用复制. 然后它也还能保留const.

    这样就能完美转发(perfect forwarding)所有情况了.

    #include<iostream>
    #include<string>
    using namespace std;
    template<typename T1, typename T2>
    void set(T1 && var1, T2 && var2){
             T1  m_var1 = std::forward<T1>(var1);
             T2 m_var2 = std::forward<T2>(var2);
    }
    
    
    int main()
    {
            string str1("hello");
            string str2("world");
            set(str1, str2);
            set("temporary str1","temporary str2");
            return 0;
    }
    root@ubuntu:~/c++# g++ -std=c++11  rvalue2.cpp -o rvalue
    #include<iostream>
    #include<string>
    using namespace std;
    template<typename T1, typename T2>
    void set(T1 && var1, T2 && var2){
             T1  m_var1 = std::forward<T1>(var1);
             T2 m_var2 = std::forward<T2>(var2);
    }
    
    
    void set2(string && var1, string && var2){
    }
    int main()
    {
            string str1("hello");
            string str2("world");
            set(str1, str2);
            set("temporary str1","temporary str2");
            set2(str1, str2);
            return 0;
    }
    root@ubuntu:~/c++# g++ -std=c++11  rvalue2.cpp -o rvalue
    rvalue2.cpp: In function ‘int main()’:
    rvalue2.cpp:19:17: error: cannot bind ‘std::__cxx11::string {aka std::__cxx11::basic_string<char>}’ lvalue to ‘std::__cxx11::string&& {aka std::__cxx11::basic_string<char>&&}’
      set2(str1, str2);
                     ^
    rvalue2.cpp:11:6: note:   initializing argument 1 of ‘void set2(std::__cxx11::string&&, std::__cxx11::string&&)’
     void set2(string && var1, string && var2){
          ^

    那我们有了forward为什么还要用move?

    技术上来说, forward确实可以替代所有的move.

    可能会有人在这里和我杠吧. 这你得去和"Effective Modern C ++"的作者Scott Meyers去争了:

    "From a purely technical perspective, the answer is yes: std::forward can do it all. std::move isn’t necessary. "
     

    但还有一些问题:

    首先, forward常用于template函数中, 使用的时候必须要多带一个template参数T: forward<T>, 代码略复杂;

    还有, 明确只需要move的情况而用forward, 代码意图不清晰, 其他人看着理解起来比较费劲.

    更技术上来说, 他们都可以被static_cast替代. 为什么不用static_cast呢? 也就是为了读着方便易懂.

  • 相关阅读:
    辅助性的“比较操作符”
    辅助性的“比较操作符”
    浙江一乘客没赶上火车退票不成把票撕了 结果"悲剧"了
    美国超震撼短片-梦想
    在HTML文件中加入空格
    揭秘人造肉
    不能发布网站简讯
    KMPlayer
    文件四处盖章签字等
    冬天到了如何御寒
  • 原文地址:https://www.cnblogs.com/dream397/p/14621119.html
Copyright © 2011-2022 走看看