備忘録です
int&
が左辺値参照、int&&
が右辺値参照。
.cpp
int x = 1;
int& a = 1; //エラー
int&& b = 1; //OK
int& c = x; //OK
int&& d = x; //エラー
(どのサイトにも似たような説明が書かれているので、詳細は省きます)
もともとあったのが左辺値参照(int&)
右辺値参照も代入できるようにしたのがconst左辺値参照(const int&)
.cpp
int x = 1;
const int& e = 1; //OK
const int& f = x; //OK
const左辺値参照(const int&)では、左辺値参照と右辺値参照の区別がつかない。
そこでバージョンC++11から左辺値(x変数)を代入できないようにした右辺値参照(int &&)が登場。
なぜ右辺値参照が必要なの?
関数を呼び分けるためです。
.cpp
void f(int& a) { }
void f(int&& a) { }
int main() {
int x = 1;
f(1); //int&&が呼ばれる
f(x); //int&が呼ばれる
}
関数を呼び分けて何が嬉しいの?
ムーブとコピーが実現できる。
a.cpp
#include <utility>
#include <cstring>
class A {
public:
char* addr;
std::size_t size;
A(A&& x) { //右辺値参照(ムーブ)
size = x.size;
addr = x.addr; //アドレスをそのまま代入
x.addr = nullptr; //x.addrをnullptrにしたので、xをデストラクトしてもdelete[]されない
}
A(A& x) { //左辺値参照(コピー)
size = x.size;
addr = new char[size]; //新しくヒープ領域を確保
std::memcpy(addr, x.addr, size);
}
A(int x) {
size = x;
addr = new char[size];
std::memset(addr, 0, size);
}
~A() { //デストラクター
if(addr != nullptr) {
delete[](addr);
addr = nullptr;
}
}
};
int main() {
A x(1); //A(int x)が呼ばれる
A a = 1; //A(int x)が呼ばれる
A b = std::move(x); //A(int&& x)が呼ばれる
A c = a; //A(int& x)が呼ばれる
//x変数はb変数にムーブしたので、x変数のデストラクターではメモリ開放(delete[])されない
}
std::move()とstd::forward()
std::move()は何でもかんでも右辺値参照にしてしまう。それだと困る場合はstd::forward()を使う。
b.cpp
#include <iostream>
#include <utility>
void f(int& x) { std::cout << "mached: &" << std::endl;}
void f(const int& x) { std::cout << "mached: const &" << std::endl;}
void f(int&& x) { std::cout << "mached: &&" << std::endl; }
void f(const int&& x) { std::cout << "mached: const &&" << std::endl;}
template<typename T> void t1(T&& obj) { f(std::move(obj)); }
template<typename T> void t2(T&& obj) { f(std::forward<T>(obj)); }
int main()
{
int a = 1;
const int b = 2;
int& c(a);
const int& d(a);
int&& e = std::move(a);
const int&& f = std::move(a);
std::cout << "std::move()" << std::endl;
t1(3); // mached: &&
t1(a); // mached: &&
t1(b); // mached: const &&
t1(c); // mached: &&
t1(d); // mached: const &&
t1(e); // mached: &&
t1(f); // mached: const &&
std::cout << "std::forward()" << std::endl;
t2(4); // mached: &&
t2(a); // mached: &
t2(b); // mached: const &
t2(c); // mached: &
t2(d); // mached: const &
t2(e); // mached: &
t2(f); // mached: const &
t2(std::move(e)); // mached: &&
t2(std::move(f)); // mached: const &&
}
std::move()とstd::forward()の実体
実体はstatic_castです。
b.cpp
(差分のみ)
//std::move()の真似
template<typename T> typename std::remove_reference<T>::type&& u_move(T&& obj) { return static_cast<typename std::remove_reference<T>::type&&>(obj); }
//std::forward()の真似
template<typename T> T&& u_forward(typename std::remove_reference<T>::type& obj) { return static_cast<T&&>(obj); }
template<typename T> void t1(T&& obj) { f(u_move(obj)); } //std::moveをu_moveに変更
template<typename T> void t2(T&& obj) { f(u_forward<T>(obj)); } //std::forwardをu_forwardに変更
ユニバーサル参照(Universal Reference)
.cpp
template<typename T> void f(T&& x) {} //関数テンプレートのT&&型
template<typename T> class A { A(T&& x) { } }; //クラステンプレートのT&&型
template<typename T> T&& x1; //変数テンプレートのT&&型
template<typename T> using B = A<T&&>; //エイリアステンプレート(型の別名)のT&&型
auto&& x2 = 1; //auto&&型
この場合の&&は通常の右辺値参照とは異なる動作をする。
ユニバーサル参照は俗称、C++17仕様において「転送参照(Forwarding Reference)」という正式名称が与えられた。