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呢? 也就是为了读着方便易懂.

 

posted on 2021-04-06 11:39  tycoon3  阅读(772)  评论(0编辑  收藏  举报

导航