C++非完美转发示例


sd

1 使用非常量左值引用转发

2 使用常量左值引用

3 使用非常量左值引用 + 常量左值引用

4 使用常量左值引用 + const_cast

5 非常量左值引用 + 修改的参数推导规则

6 右值引用

7 右值引用 + 修改的参数推导规则

1 使用非常量左值引用转发

1
2
3
4
5
6
7
8
9
void F(int a)
{
cout << a << endl;
}
template<class A>
void G(A &a)
{
F(a);
}
  • 使用非常量左值引用时,我们可以调用F(10),
  • 但无法调用G(10),即我们无法接收非常量右值的参数。

2 使用常量左值引用

1
2
3
4
5
6
7
8
9
10
void F(int &a)
{
cout << a << endl;
}

template<class A>
void G(const A &a)
{
F(a);
}
  • 使用常量左值引用时,函数G可以接收任意类型的值作为参数,包括非常量左值、常量左值、非常量右值和常量右值。
  • 但当F的参数类型为非常量左值引用时,我们无法将一个常量左值引用转发给一个非常量左值引用。

3 使用非常量左值引用 + 常量左值引用

1
2
3
4
5
6
7
8
9
10
11
template<class A>
void G(A &a)
{
F(a);
}

template<class A>
void G(const A &a)
{
F(a);
}
  • 种方案相当于对函数G进行了重载,此时可以接收任意类型的值作为参数,也可以顺利地实现转发。
  • 但由于使用了常量和非常量两种形式的重载,当参数的个数N较大时,需要重载的函数会呈指数级增长(2的N次方),因此这种方案实际上是不可取的。

4 使用常量左值引用 + const_cast

1
2
3
4
5
template<class A>
void G(const A &a)
{
F(const_cast<A &>(a));
}
  • 这种方案克服了方案二的缺点,现在可以将常量左值引用转发给非常量左值引用了。
  • 但这又带来了新的问题,假如F的参数是一个非常量左值引用,则调用G后,我们可以通过F来修改传入的常量左值和常量右值了,而这是非常危险的。

5 非常量左值引用 + 修改的参数推导规则

1
2
3
4
5
6
7
8
9
10
template<class A>
void F(A &a)
{
cout << "void F(A& a)" << endl;
}

void F(const long &a)
{
cout << "void F(const long &a)" << endl;
}
  • 在未修改参数推导规则前,调用F(10)会选择第二个重载函数,但修改后,却会调用第一个重载函数,这就给C++带来了兼容性的问题。

6 右值引用

1
2
3
4
5
template<class A>
void G(A &&a)
{
F(a);
}
  • G将无法接收左值,因为不能将一个左值传递给一个右值引用。
  • 当传递非常量右值时也会存在问题,因为此时a本身是一个左值,这样当F的参数是一个非常量左值引用时,我们就可以来修改传入的非常量右值了。

7 右值引用 + 修改的参数推导规则

引用叠加规则:

1
2
3
4
5
template<class A>
void G(A &&a)
{
F(static_cast<A &&>(a));
}
  • 当传给G一个左值(类型为T)时,由于模板是一个引用类型,因此它被隐式装换为左值引用类型T&,根据推导规则1,模板参数A被推导为T&。
  • 在G内部调用F(static_cast<A &&>(a))时,static_cast<A &&>(a)等同于static_cast<T& &&>(a),根据引用叠加规则第2点,即为static_cast<T&>(a),这样转发给F的还是一个左值。

当传给G一个左值(类型为T)时,由于模板是一个引用类型,因此它被隐式装换为左值引用类型T&,根据推导规则1,模板参数A被推导为T&。这样,在G内部调用F(static_cast<A &&>(a))时,static_cast<A &&>(a)等同于static_cast<T& &&>(a),根据引用叠加规则第2点,即为static_cast<T&>(a),这样转发给F的还是一个左值。 当传给G一个右值(类型为T)时,由于模板是一个引用类型,因此它被隐式装换为右值引用类型T&&,根据推导规则2,模板参数A被推导为T。这样,在G内部调用F(static_cast<A &&>(a))时,static_cast<A &&>(a)等同于static_cast<T&&>(a),这样转发给F的还是一个右值(不具名右值引用是右值)。 可见,使用该方案后,左值和右值都能正确地进行转发,并且不会带来其他问题。另外,C++ 11为了方便转发的实现,提供了一个函数模板forward,用于参数的完美转发。使用forward后的代码可简化为:

1
2
3
4
5
template<class A>
void G(A &&a)
{
F(forward<A>(a));
}

相关参考

C++ 11完美转发