Skip to the content.

23 std::movestd::forward 只是一种强制类型转换

#include <cassert>
#include <string>
#include <type_traits>
#include <utility>

namespace jc {

template <typename T>
constexpr std::remove_reference_t<T>&& move(T&& x) noexcept {
  return static_cast<std::remove_reference_t<T>&&>(x);
}

constexpr int f(const std::string&) { return 1; }
constexpr int f(std::string&&) { return 2; }

}  // namespace jc

int main() {
  std::string s;
  static_assert(jc::f(s) == 1);
  assert(jc::f(std::string{}) == 2);
  static_assert(jc::f(static_cast<std::string&&>(s)) == 2);
  static_assert(jc::f(jc::move(s)) == 2);
  static_assert(jc::f(std::move(s)) == 2);
}
#include <utility>

namespace jc {

constexpr int f(int&&) { return 1; }
constexpr int f(const int&) { return 2; }

}  // namespace jc

int main() {
  const int i = 1;
  static_assert(jc::f(std::move(i)) == 2);
}
#include <string>
#include <utility>

class A {
 public:
  /*
   * s 转为 const std::string&&
   * 调用 std::string(const std::string&)
   */
  explicit A(const std::string s) : s_(std::move(s)) {}

 private:
  std::string s_;
};
#include <string>
#include <utility>

class A {
 public:
  /*
   * s 转为 std::string&&
   * 调用 std::string(std::string&&)
   */
  explicit A(std::string s) : s_(std::move(s)) {}

 private:
  std::string s_;
};
#include <iostream>

void f(int&) { std::cout << 1; }
void f(const int&) { std::cout << 2; }

// 用多个重载转发给对应版本比较繁琐
void g(int& x) { f(x); }

void g(const int& x) { f(x); }

// 同样的功能可以用一个模板替代
template <typename T>
void h(T& x) {
  f(x);
}

int main() {
  int a = 1;
  const int b = 1;

  g(a);
  h(a);  // 11
  g(b);
  h(b);  // 22
  g(1);  // 2
  // h(1);  // 错误
}
#include <iostream>
#include <utility>

void f(int&) { std::cout << 1; }
void f(const int&) { std::cout << 2; }
void f(int&&) { std::cout << 3; }

// 用多个重载转发给对应版本比较繁琐
void g(int& x) { f(x); }
void g(const int& x) { f(x); }
void g(int&& x) { f(std::move(x)); }

// 用一个模板来替代上述功能
template <typename T>
void h(T&& x) {
  f(std::forward<T>(x));
}

int main() {
  int a = 1;
  const int b = 1;

  g(a);
  h(a);  // 11
  g(b);
  h(b);  // 22
  g(std::move(a));
  h(std::move(a));  // 33
  g(1);
  h(1);  // 33
}
h(std::forward<int>(a));  // 3
h(std::move(a));          // 3

24 转发引用与右值引用的区别

template <typename T>
void f(T&&) {}  // T&&不一定是右值引用

int a = 1;
f(a);  // T 推断为 int&,T&& 是 int& &&,折叠为 int&,是左值引用
f(1);  // T 推断为 int,T&& 是 int&&,右值引用
auto&& b = a;  // int& b = a,左值引用
auto&& c = 1;  // int&& c = 1,右值引用
template <typename T>
void f(std::vector<T>&&) {}  // 右值引用而非转发引用

std::vector<int> v;
f(v);  // 错误

template <typename T>
void g(const T&&) {}  // 右值引用而非转发引用

int i = 1;
g(i);  // 错误
template <class T, class Allocator = allocator<T>>
class vector {
 public:
  void push_back(T&& x);  // 右值引用

  template <class... Args>
  void emplace_back(Args&&... args);  // 转发引用
};

std::vector<A> v;  // 实例化指定了 T

// 对应的实例化为
class vector<A, allocator<A>> {
 public:
  void push_back(A&& x);  // 不涉及类型推断,右值引用

  template <class... Args>
  void emplace_back(Args&&... args);  // 转发引用
};
template <typename T>
void f(T x) {
  auto&& res = do_something(x);
  do_something_else(res);
  set(std::forward<decltype(res)>(res));
}
auto f = [](auto&& x) { return g(std::forward<decltype(x)>(x)); };

// 转发任意数量实参
auto f = [](auto&&... args) {
  return g(std::forward<decltype(args)>(args)...);
};

25 对右值引用使用 std::move,对转发引用使用 std::forward

class A {
 public:
  A(A&& rhs) : s_(std::move(rhs.s_)), p_(std::move(rhs.p_)) {}

  template <typename T>
  void f(T&& x) {
    s_ = std::forward<T>(x);
  }

 private:
  std::string s_;
  std::shared_ptr<int> p_;
};
#include <iostream>
#include <type_traits>

struct A {
  A() = default;
  A(const A&) { std::cout << 1; }
  A(A&&) { std::cout << 2; }
};

struct B {
  B() {}
  B(const B&) noexcept { std::cout << 3; }
  B(B&&) noexcept { std::cout << 4; }
};

int main() {
  A a;
  A a2 = std::move_if_noexcept(a);  // 1
  B b;
  B b2 = std::move_if_noexcept(b);  // 4
}
A f(A&& a) {
  do_something(a);
  return std::move(a);
}

template <typename T>
A g(T&& x) {
  do_something(x);
  return std::forward<T>(x);
}
A make_a() {
  A a;
  return std::move(a);  // 画蛇添足
}
A make_a() { return A{}; }

auto x = make_a();  // 只需要调用一次 A 的默认构造函数

26 避免重载使用转发引用的函数

#include <string>
#include <vector>

std::vector<std::string> v;

void f(const std::string& s) { v.emplace_back(s); }

int main() {
  // 传入右值,执行的依然是拷贝
  f(std::string{"hi"});
  f("hi");
}
#include <string>
#include <utility>
#include <vector>

std::vector<std::string> v;

template <typename T>
void f(T&& s) {
  v.emplace_back(std::forward<T>(s));
}

int main() {
  // 现在传入右值时变为移动操作
  f(std::string{"hi"});
  f("hi");
}
#include <string>
#include <utility>
#include <vector>

std::vector<std::string> v;

template <typename T>
void f(T&& s) {
  v.emplace_back(std::forward<T>(s));
}

std::string make_string(int n) { return std::string("hi"); }

void f(int n) { v.emplace_back(make_string(n)); }

int main() {
  // 之前的调用仍然正常
  f(std::string{"hi"});
  f("hi");
  // 对于重载版本的调用也没问题
  f(1);  // 调用重载版本
  // 但对于非 int(即使能转换到 int)参数就会出现问题
  int i = 1;
  /*
   * 转发引用是比 int 更精确的匹配
   * 为 std::vector<std::string> 传入 short
   * 用 short 构造 std::string 导致错误
   */
  f(i);
}
#include <string>
#include <utility>

std::string make_string(int n) { return std::string{"hi"}; }

class A {
 public:
  A() = default;

  template <typename T>
  explicit A(T&& x) : s_(std::forward<T>(x)) {}

  explicit A(int x) : s_(make_string(x)) {}

 private:
  std::string s_;
};

int main() {
  int i = 1;
  A a{i};     // 依然调用模板而出错,但还有一个更大的问题
  A b{"hi"};  // OK
  A c{b};  // 错误:调用的仍是模板,用 A 初始化 std::string 出错
}
#include <string>
#include <utility>

class A {
 public:
  template <typename T>
  explicit A(T&& x) : s_(std::forward<T>(x)) {}

  A(const A& rhs) = default;
  A(A&& rhs) = default;

 private:
  std::string s_;
};

int main() {
  A a{"hi"};  // OK
  A b{a};  // 错误:T&& 比 const A& 更匹配,调用模板用 A 初始化 std::string 出错
  const A c{"hi"};
  A d{c};  // OK
}
#include <string>
#include <utility>

class A {
 public:
  template <typename T>
  explicit A(T&& n) : s(std::forward<T>(n)) {}

 private:
  std::string s;
};

class B : public A {
 public:
  /*
   * 错误:调用基类模板而非拷贝构造函数
   * const B 不能转为 std::string
   */
  B(const B& rhs) : A(rhs) {}

  /*
   * 错误:调用基类模板而非移动构造函数
   * B 不能转为 std::string
   */
  B(B&& rhs) noexcept : A(std::move(rhs)) {}
};

27 重载转发引用的替代方案

#include <string>

class A {
 public:
  template <typename T>
  explicit A(const T& x) : s_(x) {}

 private:
  std::string s_;
};

int main() {
  A a{"hi"};
  A b{a};  // OK
}
#include <string>
#include <utility>

std::string make_string(int n) { return std::string{"hi"}; }

class A {
 public:
  explicit A(std::string s) : s_(std::move(s)) {}
  explicit A(int n) : s_(make_string(n)) {}

 private:
  std::string s_;
};

int main() {
  int i = 1;
  A a{i};  // OK,调用 int 版本的构造函数
}

标签分派(tag dispatching)

#include <string>
#include <type_traits>
#include <utility>
#include <vector>

std::vector<std::string> v;

template <typename T>
void g(T&& s, std::false_type) {
  v.emplace_back(std::forward<T>(s));
}

std::string make_string(int n) { return std::string{"hi"}; }

void g(int n, std::true_type) { v.emplace_back(make_string(n)); }

template <typename T>
void f(T&& s) {
  g(std::forward<T>(s), std::is_integral<std::remove_reference_t<T>>());
}

int main() {
  int i = 1;
  f(i);  // OK:调用 int 版本
}

使用 std::enable_if 在特定条件下禁用模板

#include <string>
#include <type_traits>

class A {
 public:
  template <typename T,
            typename = std::enable_if_t<!std::is_same_v<A, std::decay_t<T>>>>
  explicit A(T&& x) {}

 private:
  std::string s_;
};
#include <string>
#include <type_traits>
#include <utility>

class A {
 public:
  template <typename T,
            typename = std::enable_if_t<!std::is_base_of_v<A, std::decay_t<T>>>>
  explicit A(T&& x) {}

 private:
  std::string s_;
};

class B : public A {
 public:
  B(const B& rhs) : A(rhs) {}                 // OK:不再调用模板
  B(B&& rhs) : A(std::move(rhs)) noexcept {}  // OK:不再调用模板
};
#include <string>
#include <type_traits>
#include <utility>

std::string make_string(int n) { return std::string{"hi"}; }

class A {
 public:
  template <typename T, typename = std::enable_if_t<
                            !std::is_base_of_v<A, std::decay_t<T>> &&
                            !std::is_integral_v<std::remove_reference_t<T>>>>
  explicit A(T&& x) : s_(std::forward<T>(x)) {
    static_assert(std::is_constructible_v<std::string, T>,
                  "Parameter n can't be used to construct a std::string");
  }

  explicit A(int n) : s_(make_string(n)) {}

 private:
  std::string s_;
};

int main() {
  int i = 1;
  A a{1};     // OK:调用 int 版本的构造函数
  A b{"hi"};  // OK
  A c{b};     // OK
}

28 引用折叠

int a = 1;
int& & b = a;  // 错误
template <typename T>
void f(T&&);

int i = 1;
f(i);  // T 为 int&,T& && 变成了引用的引用,于是需要引用折叠的机制
& + &  &
& + &&  &
&& + &  &
&& + &&  &&
#include <type_traits>

namespace jc {

// 如果传递左值 A,T 推断为 A&,此时需要引用折叠
template <typename T>
constexpr T&& forward(std::remove_reference_t<T>& t) noexcept {
  return static_cast<T&&>(t);
}

/*
 * 传递左值 A 时相当于
 * A&&& forward(std::remove_reference_t<A&>& x) { return static_cast<A&&&>(x); }
 * 简化后为
 * A& forward(A& x) { return static_cast<A&>(x); }
 * 传递右值 A 相当于
 * A&& forward(std::remove_reference_t<A>& x) { return static_cast<A&&>(x); }
 * 简化后为
 * A&& forward(A& x) { return static_cast<A&&>(x); }
 */

}  // namespace jc
int a = 1;
auto&& b = a;  // a 是左值,auto 被推断为 int&,int& && 折叠为 int&
template <typename T>
struct A {
  using RvalueRef = T&&;  // typedef T&& RvalueRef
};

int a = 1;
A<int&>::RvalueRef b = a;  // int& && 折叠为 int&,int& b = a
using A = const int&;  // low-level
using B = int&&;       // low-level
static_assert(std::is_same_v<volatile A&&, const int&>);
static_assert(std::is_same_v<const B&&, int&&>);

29 移动不比拷贝快的情况

30 无法完美转发的类型

大括号初始化

void f(const std::vector<int>& v) {}

template <typename T>
void fwd(T&& x) {
  f(std::forward<T>(x));
}

f({1, 2, 3});    // OK,{1, 2, 3} 隐式转换为 std::vector<int>
fwd({1, 2, 3});  // 无法推断 T,导致编译错误

// 解决方法是借用 auto 推断出 std::initializer_list 类型再转发
auto x = {1, 2, 3};
fwd(x);  // OK

作为空指针的 0 或 NULL

void f(int*) {}

template <typename T>
void fwd(T&& x) {
  f(std::forward<T>(x));
}

fwd(NULL);  // T 推断为 int,转发失败

只声明但未定义的 static const 整型数据成员

class A {
 public:
  static const int n = 1;  // 仅声明
};

void f(int) {}

template <typename T>
void fwd(T&& x) {
  f(std::forward<T>(x));
}

f(A::n);    // OK:等价于 f(1)
fwd(A::n);  // 错误:fwd 形参是转发引用,需要取址,无法链接
// A.h
class A {
 public:
  static const int n = 1;
};

// A.cpp
const int A::n;

重载函数的名称和函数模板名称

void g(int) {}

void f(void (*pf)(int)) {}

template <typename T>
void fwd(T&& x) {
  f(std::forward<T>(x));
}

f(g);    // OK
fwd(g);  // OK
void g(int) {}
void g(int, int) {}

void f(void (*)(int)) {}

template <typename T>
void fwd(T&& x) {
  f(std::forward<T>(x));
}

f(g);    // OK
fwd(g);  // 错误:不知道转发的是哪一个函数指针
template <typename T>
void g(T x) {
  std::cout << x;
}

void f(void (*pf)(int)) { pf(1); }

template <typename T>
void fwd(T&& x) {
  f(std::forward<T>(x));
}

f(g);         // OK
fwd(g<int>);  // 错误
template <typename T>
void g(T x) {
  std::cout << x;
}

void f(void (*pf)(int)) { pf(1); }

template <typename T>
void fwd(T&& x) {
  f(std::forward<T>(x));
}

using PF = void (*)(int);
PF p = g;
fwd(p);                   // OK
fwd(static_cast<PF>(g));  // OK

位域

struct A {
  int a : 1;
  int b : 1;
};

void f(int) {}

template <typename T>
void fwd(T&& x) {
  f(std::forward<T>(x));
}

A x{};
f(x.a);    // OK
fwd(x.a);  // 错误
fwd(static_cast<int>(x.a));  // OK