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