sd
1 使用非常量左值引用转发
2 使用常量左值引用
3 使用非常量左值引用 + 常量左值引用
4 使用常量左值引用 + const_cast
5 非常量左值引用 + 修改的参数推导规则
6 右值引用
7 右值引用 + 修改的参数推导规则
1 使用非常量左值引用转发
1 | void F(int a) |
- 使用非常量左值引用时,我们可以调用F(10),
- 但无法调用G(10),即我们无法接收非常量右值的参数。
2 使用常量左值引用
1 | void F(int &a) |
- 使用常量左值引用时,函数G可以接收任意类型的值作为参数,包括非常量左值、常量左值、非常量右值和常量右值。
- 但当F的参数类型为非常量左值引用时,我们无法将一个常量左值引用转发给一个非常量左值引用。
3 使用非常量左值引用 + 常量左值引用
1 | template<class A> |
- 种方案相当于对函数G进行了重载,此时可以接收任意类型的值作为参数,也可以顺利地实现转发。
- 但由于使用了常量和非常量两种形式的重载,当参数的个数N较大时,需要重载的函数会呈指数级增长(2的N次方),因此这种方案实际上是不可取的。
4 使用常量左值引用 + const_cast
1 | template<class A> |
- 这种方案克服了方案二的缺点,现在可以将常量左值引用转发给非常量左值引用了。
- 但这又带来了新的问题,假如F的参数是一个非常量左值引用,则调用G后,我们可以通过F来修改传入的常量左值和常量右值了,而这是非常危险的。
5 非常量左值引用 + 修改的参数推导规则
1 | template<class A> |
- 在未修改参数推导规则前,调用F(10)会选择第二个重载函数,但修改后,却会调用第一个重载函数,这就给C++带来了兼容性的问题。
6 右值引用
1 | template<class A> |
- G将无法接收左值,因为不能将一个左值传递给一个右值引用。
- 当传递非常量右值时也会存在问题,因为此时a本身是一个左值,这样当F的参数是一个非常量左值引用时,我们就可以来修改传入的非常量右值了。
7 右值引用 + 修改的参数推导规则
引用叠加规则:
1 | template<class 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 | template<class A> |