target forward function

std::target在C++中主要用于获取std::function对象所包装的可调用对象的指针或引用。‌具体来说,‌std::function是一个通用的、‌类型无关的函数包装器,‌可以存储、‌复制和调用任何可调用的目标——函数、‌lambda表达式或者其他函数对象,‌还有指向成员函数指针等。‌std::functiontarget成员函数返回一个指向被包装的可调用对象的指针或引用。‌这个功能在需要检查或操作底层可调用对象时非常有用,‌例如,‌当你想要确定一个std::function对象是否指向一个特定的函数或方法时,‌可以使用std::function::target来获取这个信息1。‌

此外,‌target的概念在不同的技术领域和上下文中可能有不同的应用。‌例如,‌在软件开发中,‌特别是在编译过程中,‌"target"通常指的是编译的目标平台或环境,‌如特定操作系统、‌硬件架构或JavaScript的运行环境(‌浏览器或Node.js)‌。‌通过设置正确的目标,‌开发人员可以确保生成的代码在特定的环境中具有最佳的兼容性和性能2。‌

然而,‌需要注意的是,‌std::target是C++标准库中的一个成员函数,‌而"target"在其他技术领域和上下文中可能有不同的含义或用途。‌因此,‌理解具体的上下文是非常重要的。‌

333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333

C++多值返回:6种方式秒杀这道面试题

Francis

Francis

​关注

45 人赞同了该文章

目录

收起

1. 指针与引用

2. TUPLE + TIE

3. STRUCT BINDING

4. 函数 CALLBACK

5. 自定义 OUT

6. 模版推导

原文(可获取本文完整源码):

C++ 多值返回:从版本1到版本6秒杀​​mp.weixin.qq.com/s/VEvUxpcJPsxT9kL7-zLTxg​编辑

今天再提出一个比较经典的面试题:函数如何返回多个结果?你能想到几种办法?

其实这道题目考察的知识点非常的多,涉及到:

  • 指针
  • 引用
  • lambda
  • functional
  • tie
  • tuple
  • struct bingding
  • template deduce
  • 等等

本篇文章将会给大家用不同的方法来解答这道题目,首先我们转化一下这个题目,使用a / b来模拟(下面不考虑被除数为0的场景),返回商与余数。简单来说转化为一个函数返回商与余数有多少种办法?

下面进入正文。

1. 指针与引用

对接口进行更改,添加两个变量,使用指针与引用。

void divideWithReferences(int dividend, int divisor, int& quotient, int& remainder) {
  quotient = dividend / divisor;
  remainder = dividend % divisor;
}
​
void divideWithPointers(int dividend, int divisor, int* quotient, int* remainder) {
  if (quotient) *quotient = dividend / divisor;
  if (remainder) *remainder = dividend % divisor;
}
​

使用这种方式的缺点也明显可见,当返回值不止两个,接口就非常的长。此外,如果面试官要求你不允许更改函数接口,那么这个办法用不了了。

2. TUPLE + TIE

Tuple和tie的组合为函数返回多个值提供了一种简洁的方式。通过使用std::tie,我们可以将tuple中的元素解包到不同的变量中,提高代码的清晰度。

这种方法算是标准答案之一,比较简单清晰,如果返回值是两个pair也可以。

std::tuple<int, int> divide(int dividend, int divisor) {
  return std::make_tuple(dividend / divisor, dividend % divisor);
}
​
​
std::tie(quotient, remainder) = divide(14, 3);
std::cout << quotient << ", " << remainder << std::endl;

3. STRUCT BINDING

对上面进行改造,C++17引入的结构化绑定进一步简化了多值返回的代码。使用auto和结构化绑定,使得代码更加直观易懂。

如果面试官要你进一步的对上面的方法进行改造,那么可以变为下面这个代码示例,使用结构化绑定使代码更加优雅。

auto divide(int dividend, int divisor) {
  struct result {
    int quotient;
    int remainder;
  };
  return result{dividend / divisor, dividend % divisor};
}
auto [quotient, remainder] = divide(14, 3);

4. 函数 CALLBACK

在日常项目中,特别是一些开源项目,callback是一种常用的手段,通过传递处理返回值的callback,让用户自定义处理,这样便实现了返回多个值,实现更加灵活的代码结构。这对于异步编程和事件处理等场景非常有用。

void divide(int dividend, int divisor, std::function<void(int, int)> callback) {
  callback(dividend / divisor, dividend % divisor);
}

5. 自定义 OUT

自定义out是一种通过结构体包装的方式,将输出参数作为结构体的成员。这种方式提高了代码的可读性,尤其适用于需要返回多个值的函数。

template <class T>
struct out {
  std::function<void(T)> target;
​
  out(T* t)
      : target([t](T&& in) {
          if (t) *t = std::move(in);
        }) {}
​
  template <class... Args>
  void emplace(Args&&... args) {
    target(T(std::forward<Args>(args)...));
  }
​
  template <class X>
  void operator=(X&& x) {
    emplace(std::forward<X>(x));
  }
​
  template <class... Args>
  void operator()(Args&&... args) {
    emplace(std::forward<Args>(args)...);
  }
};
​
void divide(int dividend, int divisor, out<int>& quotient_out, out<int>& remainder_out) {
  quotient_out.emplace(dividend / divisor);
  remainder_out.emplace(dividend % divisor);
}

6. 模版推导

C++的模版推导为开发者提供了更为灵活和简洁的代码编写方式。通过模版推导,我们可以处理不同类型的数据而无需显式指定类型。

template <typename T1, typename T2>
struct many {
  T1 quotient;
  T2 remainder;
};
​
template <class T1, class T2>
many(T1, T2) -> many<T1, T2>;
​
many<int, int> divide(int dividend, int divisor) {
  return many{
      dividend / divisor,
      dividend % divisor,
  };
}
​
auto [quotient, remainder] = divide(14, 3);

总体而言,C++提供了多种方式来实现多值返回,每种方式都有其适用的场景。选择合适的方式取决于具体的需求,开发者可以根据代码结构和可读性来灵活使用这些特性。

1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111

c++比较std::function,以及target()的坑

飞翔的蚯蚓

已于 2024-06-04 21:39:39 修改

阅读量3.7k
 收藏 10

点赞数 4
版权

std::function
成员函数
std::bind
target_type
std::_Bind
关键词由CSDN通过智能技术生成
1.c++ std::function调用member function的方法
class Button
{
public:
    void onclick(int value)
    {
        cout<<"will print value\n";
    }
};
 
int main(int argc,char *argv[])
{
    function<void(Button,int)> buttonClick;
    buttonClick = &Button::onclick;
    Button button;
    buttonClick(button,1);
    return 0;
}
#include<iostream>
#include<functional>
using std::cout;
using std::function;
 
class Button
{
public:
    void onclick(int value)
    {
        cout<<"will print value\n";
    }
};
 
int main(int argc,char *argv[])
{
    function<void(int)> buttonClick = std::bind(&Button::onclick, button, std::placeholders::_1);
    buttonClick(1);
    return 0;
}
2.比较std::function
这里用到了function的target(),根据官方文档来看,这个函数返回指向存储在函数对象中的可调用对象的指针。先来看一个实例。

#include <functional>
#include <iostream>
 
struct foo{ int f(int a) { return a; } };
 
int fun(int a) { return a; }
 
int main()
{
    // fn1 and fn2 have the same type, but their targets do not
    foo f;
    std::function<int(int)> fn1(std::bind(&foo::f, &f, std::placeholders::_1));
    std::function<int(int)> fn2(fun);
    std::cout << fn1.target_type().name() << '\n';
    std::cout << fn2.target_type().name() << '\n';
              
    typedef int(*funptr)(int);
    funptr *fptr = fn1.target<funptr>();
    if(!fptr) {
        std::cout << "fn1 nullptr\n";
    }
    
    fptr = fn2.target<funptr>();
    if(!fptr) {
        std::cout << "fn2 nullptr\n";
    }
}
在我们运行完后,居然发现fn1是个nullptr,这是为什么呢?原因是这里我们使用了std::bind(),所以我们此时实际的target的参数并不是funptr,而是

std::_Bind<int (foo::*(foo *, std::_Placeholder<1>)(int)>;
所以我们的代码应该改为:

#include <functional>
#include <iostream>
 
struct foo{ int f(int a) { return a; } };
 
int fun(int a) { return a; }
 
int main()
{
    // fn1 and fn2 have the same type, but their targets do not
    foo f;
    std::function<int(int)> fn1(std::bind(&foo::f, &f, std::placeholders::_1));
    std::function<int(int)> fn2(fun);
    std::cout << fn1.target_type().name() << '\n';
    std::cout << fn2.target_type().name() << '\n';
              
    auto fptr = fn1.target<std::_Bind<int (foo::*(foo *, std::_Placeholder<1>)(int)>>();
    if(!fptr) {
        std::cout << "fn1 nullptr\n";
    }
}
那么我是怎么确定这个参数的呢,需要用到std::function的另一个方法:target_type().name()。

即上例的fn1.target_type.name(),我们在得到了该值后,在命令行运行如下命令:

c++filt -t St5_BindIFSt7_Mem_fnIM3barFiiiEEPS1_St12_PlaceholderILi1EES6_ILi2EEEE
最后的那个参数是我的实例中打印出的,你可以根据你的情况修改。

部分源码采用一位大佬的,来看看我的源码和结果。

#include<iostream>
#include<functional>
using std::cout;
using std::function;
 
class Button
{
public:
    void onclick(int value)
    {
        cout<<"will print value\n";
    }
};
 
int main(int argc,char *argv[])
{
    Button button;
    function<void(int)> buttonFun = std::bind(&Button::onclick, button, std::placeholders::_1);
    std::cout<<buttonFun.target_type().name()<<"\n";
    std::cout<<buttonFun.target<std::_Bind<void (Button::*(Button, std::_Placeholder<1>))(int)>>();
    (*buttonFun.target<std::_Bind<void (Button::*(Button, std::_Placeholder<1>))(int)>>())(1);
    return 0;
}
user@ubuntu:~/study/c++/test$ ./test
St5_BindIFM6ButtonFviES0_St12_PlaceholderILi1EEEE
0x55ccc430ee70will print value

飞翔的蚯蚓
关注

————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/qq_37105120/article/details/119772863

222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222

c++比较std::function,以及target()的坑

飞翔的蚯蚓

已于 2024-06-04 21:39:39 修改

阅读量3.7k
 收藏 10

点赞数 4
版权

std::function
成员函数
std::bind
target_type
std::_Bind
关键词由CSDN通过智能技术生成
1.c++ std::function调用member function的方法
class Button
{
public:
    void onclick(int value)
    {
        cout<<"will print value\n";
    }
};
 
int main(int argc,char *argv[])
{
    function<void(Button,int)> buttonClick;
    buttonClick = &Button::onclick;
    Button button;
    buttonClick(button,1);
    return 0;
}
#include<iostream>
#include<functional>
using std::cout;
using std::function;
 
class Button
{
public:
    void onclick(int value)
    {
        cout<<"will print value\n";
    }
};
 
int main(int argc,char *argv[])
{
    function<void(int)> buttonClick = std::bind(&Button::onclick, button, std::placeholders::_1);
    buttonClick(1);
    return 0;
}
2.比较std::function
这里用到了function的target(),根据官方文档来看,这个函数返回指向存储在函数对象中的可调用对象的指针。先来看一个实例。

#include <functional>
#include <iostream>
 
struct foo{ int f(int a) { return a; } };
 
int fun(int a) { return a; }
 
int main()
{
    // fn1 and fn2 have the same type, but their targets do not
    foo f;
    std::function<int(int)> fn1(std::bind(&foo::f, &f, std::placeholders::_1));
    std::function<int(int)> fn2(fun);
    std::cout << fn1.target_type().name() << '\n';
    std::cout << fn2.target_type().name() << '\n';
              
    typedef int(*funptr)(int);
    funptr *fptr = fn1.target<funptr>();
    if(!fptr) {
        std::cout << "fn1 nullptr\n";
    }
    
    fptr = fn2.target<funptr>();
    if(!fptr) {
        std::cout << "fn2 nullptr\n";
    }

}
在我们运行完后,居然发现fn1是个nullptr,这是为什么呢?原因是这里我们使用了std::bind(),所以我们此时实际的target的参数并不是funptr,而是

std::_Bind<int (foo::*(foo *, std::_Placeholder<1>)(int)>;
所以我们的代码应该改为:

#include <functional>
#include <iostream>
 
struct foo{ int f(int a) { return a; } };
 
int fun(int a) { return a; }
 
int main()
{
    // fn1 and fn2 have the same type, but their targets do not
    foo f;
    std::function<int(int)> fn1(std::bind(&foo::f, &f, std::placeholders::_1));
    std::function<int(int)> fn2(fun);
    std::cout << fn1.target_type().name() << '\n';
    std::cout << fn2.target_type().name() << '\n';
              
    auto fptr = fn1.target<std::_Bind<int (foo::*(foo *, std::_Placeholder<1>)(int)>>();
    if(!fptr) {
        std::cout << "fn1 nullptr\n";
    }
}

那么我是怎么确定这个参数的呢,需要用到std::function的另一个方法:target_type().name()。

即上例的fn1.target_type.name(),我们在得到了该值后,在命令行运行如下命令:

c++filt -t St5_BindIFSt7_Mem_fnIM3barFiiiEEPS1_St12_PlaceholderILi1EES6_ILi2EEEE
最后的那个参数是我的实例中打印出的,你可以根据你的情况修改。

部分源码采用一位大佬的,来看看我的源码和结果。

#include<iostream>
#include<functional>
using std::cout;
using std::function;
 
class Button
{
public:
    void onclick(int value)
    {
        cout<<"will print value\n";
    }
};
 
int main(int argc,char *argv[])
{
    Button button;
    function<void(int)> buttonFun = std::bind(&Button::onclick, button, std::placeholders::_1);
    std::cout<<buttonFun.target_type().name()<<"\n";
    std::cout<<buttonFun.target<std::_Bind<void (Button::*(Button, std::_Placeholder<1>))(int)>>();
    (*buttonFun.target<std::_Bind<void (Button::*(Button, std::_Placeholder<1>))(int)>>())(1);
    return 0;
}
user@ubuntu:~/study/c++/test$ ./test
St5_BindIFM6ButtonFviES0_St12_PlaceholderILi1EEEE
0x55ccc430ee70will print value

飞翔的蚯蚓
关注

4


10

0

用C+
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/qq_37105120/article/details/119772863

444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444

突破编程_C++_C++11新特性(完美转发 forward)

breakthrough_01

于 2024-03-21 20:45:52 发布

阅读量978
 收藏 7

点赞数 6
分类专栏: 突破编程_C++_C++11新特性 文章标签: c++
版权

突破编程_C++_C++11新特性
专栏收录该内容
29 篇文章2 订阅
订阅专栏
1 完美转发的概念
C++11 中引入的完美转发(Perfect Forwarding)是一种强大的编程技巧,它允许在编写泛型函数时保留参数的类型和值类别(即左值或右值),从而实现更为高效且准确地传递参数。这种技巧在编写包装器函数、代理函数或需要传递参数的函数模板时特别有用。

完美转发的核心概念在于,如果 function() 函数接收到的参数t为左值,那么该函数传递给 otherdef() 的参数 t 也应该是左值;反之,如果 function() 函数接收到的参数 t 为右值,那么传递给 otherdef() 函数的参数 t 也必须是右值。这意味着在转发过程中,参数的原始属性(包括其类型和值类别)都得到了保留。

为了实现完美转发,C++11 引入了右值引用和模板类型推导的概念。右值引用使用两个&符号(&&)来表示,它可以绑定到临时对象(右值)上,从而实现移动语义,避免不必要的拷贝操作。模板类型推导则允许编译器在编译时自动推断模板参数的类型。

在完美转发中,一个关键的概念是“万能引用”(universal reference)。对于形如 T&& 的变量或参数,如果 T 可以进行推导,那么 T&& 就称之为万能引用。万能引用既可以绑定左值,又可以绑定右值。这使得可以在函数模板中使用 T&& 作为参数类型,然后通过 std::forward 函数将参数以原始的形式转发给其他函数。

std::forward 函数是实现完美转发的关键。它的作用是将参数以原始的形式(包括其类型和值类别)转发给其他函数。std::forward 接受一个模板参数和一个参数,并返回该参数的转发引用。通过使用std::forward,我们可以确保在转发过程中不会丢失参数的原始属性。

2 引用折叠
2.1 引用折叠的基本概念
引用折叠是 C++11 中引入的一个重要概念,它在完美转发(Perfect Forwarding)中起到了关键作用。为了详细解释引用折叠的基本概念,需要再回顾一下左值引用和右值引用的概念。

左值引用是对一个已存在对象的引用,它可以使用取地址操作符&来获取地址。而右值引用则是对一个临时对象或者即将被销毁的对象的引用,通常用于支持移动语义,以提高代码性能。

引用折叠主要发生在模板函数和 std::forward 等上下文中。当在模板函数中使用 T&& 作为参数类型时,这里的 T&& 就是一个万能引用,它可以接收左值参数和右值参数。但是,仅仅依靠 T&& 并不能实现完美转发,还需要引用折叠规则来确保参数的原始属性(包括类型和值类别)在转发过程中得到保留。

引用折叠的一个典型应用场景是在实现完美转发时。通过结合 std::forward 和引用折叠,可以编写能够透明地转发参数的函数模板,确保参数在传递过程中的原始属性不会丢失。

2.2 引用折叠的规则与实例
引用折叠规则如下:

(1)左值引用与左值引用的折叠: 当左值引用与左值引用结合时,结果仍然是左值引用。具体来说,以下几种情况都会折叠为左值引用:

T& & 折叠为 T&
T& && 折叠为 T&
T& const & 折叠为 T& const(这里的 const 是保留的,表明引用本身是不可变的,但所引用的对象可能可以修改)
(2)右值引用与右值引用的折叠: 当右值引用与右值引用结合时,结果则是右值引用:

T&& && 折叠为 T&&
T&& & 折叠为 T&&
如下为样例代码:

#include <iostream>  
#include <type_traits>  

template<typename T>
void print_type(T&&) {
    std::cout << typeid(T&&).name() << std::endl;
    if (std::is_lvalue_reference<T&&>::value) {
        std::cout << "left referance" << std::endl;
    }
    else if (std::is_rvalue_reference<T&&>::value) {
        std::cout << "right referance" << std::endl;
    }
    else {
        std::cout << "not referance" << std::endl;
    }
}

int main() 
{
    int x = 5; // x 是左值  
    print_type(x); // 输出 int 的类型信息,并表明是左值引用  
    print_type(std::move(x)); // 输出 int 的类型信息,并表明是右值引用  
    print_type(12); // 输出 int 的类型信息,并表明是右值引用  
    return 0;
}

上面代码的输出为:

int
left referance
int
right referance
int
right referance

这个例子定义了一个模板函数 print_type,它接受一个万能引用 T&& 作为参数。通过调用 std::is_lvalue_reference<T>::value 和 std::is_rvalue_reference::value,可以检查 T 的类型是左值引用还是右值引用。

当传入一个左值 x 时,尽管 print_type 的参数类型是 T&&,但由于引用折叠规则,在模板实例化时,T 会被推导为 int&(左值引用)。同样地,当传入一个右值(如通过 std::move(x) 或字面量12)时,T 会被推导为 int,并且在函数内部,T&& 实际上是一个右值引用。

3 std::forward 函数
3.1 std::forward 函数的定义与作用
std::forward 是 C++11 中引入的一个模板函数,主要用于在模板函数或模板类中实现参数的完美转发(perfect forwarding)。完美转发是指函数或类模板可以将其参数原封不动地转发给另一个函数或类模板,同时保持被转发参数的左右值特性(lvalue 或 rvalue)。

定义:

std::forward的基本定义如下:

template<typename T>  
T&& forward(typename std::remove_reference<T>::type& arg);
1
2
这里,T 是一个模板参数,它代表了需要被转发的参数的原始类型。std::remove_reference<T>::type 是一个类型特性,用于从 T 中移除引用,从而得到原始类型。然后,接受一个这个原始类型的左值引用作为参数 arg。返回类型是 T&&,即 T 的右值引用。但这里通过引用折叠规则,当 arg 是左值时,返回的是左值引用;当 arg 是右值时,返回的是右值引用。

作用:

std::forward 的主要作用是在泛型编程中实现参数的完美转发。在 C++ 中,函数参数可以是左值引用(lvalue reference)或右值引用(rvalue reference)。左值引用类型的变量或参数可以修改其所绑定对象的值,而右值引用则通常用于移动语义,可以避免不必要的拷贝操作,提高性能。

当在模板函数中接收到一个参数时,此时并不知道这个参数是左值还是右值。但 std::forward 允许根据参数的原始类型(左值或右值)来转发它。这是通过引用折叠规则实现的。当将一个左值传递给 std::forward 时,它返回一个左值引用;当将一个右值传递给 std::forward 时,它返回一个右值引用。这样,就可以确保参数在转发过程中保持其原始的左右值特性。

在实际编程中,std::forward 通常与模板函数和右值引用结合使用。它可以帮助编写更加灵活和高效的泛型代码,因为开发人员可以根据参数的类型来决定是否进行拷贝操作或移动操作,从而提高程序的性能。

3.2 std::forward 函数的使用示例
如下一个 std::forward 具体的使用示例:

#include <iostream>  
#include <utility> // for std::forward  

// 一个简单的函数,接受一个右值引用并打印信息  
void printValue(int&& value) {
    std::cout << "Rvalue: " << value << std::endl;
}

// 一个简单的函数,接受一个左值引用并打印信息  
void printValue(int& value) {
    std::cout << "Lvalue: " << value << std::endl;
}

// 一个模板函数,接受任何类型的参数并完美转发给printValue  
template<typename T>
void forwarder(T&& arg) {
    printValue(std::forward<T>(arg));
}

int main()
{
    int x = 10; // 左值  
    forwarder(x); // 调用 void printValue(int& value) -> 左值引用参数

    forwarder(20); // 调用 printValue(int&& value) -> 右值引用参数 
    // 如果没有std::forward,会调用 void printValue(int& value) -> 左值引用参数

    return 0;
}

在这个示例中,forwarder 是一个模板函数,它接受一个通用引用参数 arg。在函数内部,使用 std::forward<T>(arg)来完美转发 arg 给 printValue 函数。注意,printValue 函数是重载函数,分别接受一个右值引用与一个左值引用作为参数。

3.3 完美转发的实现步骤
根据上面 “3.2 std::forward 函数的使用示例” 可以总结出完美转发的实现步骤:

(1)使用右值引用和模板类型推导

首先,需要在函数模板中使用右值引用和模板类型推导来接收参数。这通常通过 T&& 的形式来实现,其中 T 是一个模板类型参数。由于引用折叠规则,T&& 可以接收左值或右值参数,并将其推导为相应的左值引用或右值引用。

(2)调用 std::forward 进行转发

接下来,使用 std::forward 函数来转发参数。std::forward 的作用是根据传递给它的参数的原始值类别来构造一个相应类型的引用,并将其返回。这使得我们能够以参数原始的形式将其转发给另一个函数。

在调用std::forward时,我们需要同时提供模板类型参数T和实际的参数。这是因为 std::forward 需要知道参数的原始类型,以便正确地构造引用。

(3)编写转发函数

现在,可以编写一个转发函数,该函数接受任意类型和数量的参数,并使用 std::forward 将它们转发给另一个函数。转发函数的参数列表通常使用模板参数包和完美转发技术来实现。

转发函数的基本结构如下:

template<typename... Args>  
void forwarder(Args&&... args) {  
    targetFunction(std::forward<Args>(args)...);  
}

在这里,Args&&… args 表示接受任意数量和类型的参数,并使用完美转发技术将它们传递给 targetFunction。std::forward<Args>(args)… 则是使用 std::forward 来转发每个参数。

(4)调用转发函数

最后,可以调用转发函数,并将需要转发的参数传递给它。转发函数将使用 std::forward 将参数以原始的形式转发给目标函数。

注意事项

完美转发要求目标函数能够接受与转发函数参数相同类型的参数,否则会出现编译错误。
在使用完美转发时,需要确保转发函数的参数列表与目标函数的参数列表匹配,以便正确地进行转发。
完美转发主要用于泛型编程和模板元编程中,可以帮助我们编写更加灵活和通用的代码。
4 完美转发的应用实例
4.1 完美转发在包装器函数中的应用
假设有一个需要被包装的函数,它接受任意类型的参数并处理它们:

#include <iostream>  
#include <string>  
#include <utility> // for std::forward  

void processValue(int value) {  
    std::cout << "Processing int value: " << value << std::endl;  
}  
  
void processValue(const std::string& value) {  
    std::cout << "Processing string value: " << value << std::endl;  
}

现在,创建一个包装器函数,它能够接受任意类型的参数,并使用完美转发将这些参数传递给上述的 processValue 函数:

#include <iostream>  
#include <utility> // for std::forward  
  
// 包装器函数模板,接受任意类型和数量的参数  
template<typename... Args>  
void wrapperFunction(Args&&... args) {  
    // 使用完美转发将参数传递给processValue函数  
    processValue(std::forward<Args>(args)...);  
}  
  
// 之前定义的processValue函数  
// ...  
  
int main() {  
    // 调用包装器函数,并传递int类型的参数  
    wrapperFunction(12); // 输出: Processing int value: 12  
  
    // 调用包装器函数,并传递string类型的参数  
    wrapperFunction("Hello, World!"); // 输出: Processing string value: Hello, World!  
  
    return 0;  
}

在这个示例中,wrapperFunction 是一个模板函数,它接受任意数量和类型的参数(通过 Args&&… args 表示)。然后,它使用 std::forward<Args>(args)… 将参数完美转发给 processValue 函数。这样,无论传递给 wrapperFunction 的是左值还是右值,它们的值类别都会被保留并传递给 processValue,从而允许正确的重载版本被调用。

这个示例展示了完美转发在包装器函数中的一个典型应用,即允许包装器函数以透明的方式转发参数给另一个函数,同时保持参数的原始特性。这种技术使得编写通用和灵活的包装器函数变得更加容易。

4.2 完美转发在代理函数中的应用
完美转发在代理函数中的应用类似于上面的包装器函数,以下是一个简单的示例:

#include <iostream>  
#include <string>  
#include <utility> // for std::forward  

// 目标函数,接受一个int类型的参数  
void targetFunction(int value) {
    std::cout << "Target function called with value: " << value << std::endl;
}

// 代理函数模板,使用完美转发将参数传递给目标函数  
template<typename T>
void proxyFunction(T&& value) {
    targetFunction(std::forward<T>(value));
}

int main() 
{
    // 调用代理函数,传递一个左值int  
    int lvalue = 12;
    proxyFunction(lvalue); // 输出: Target function called with value: 12  

    // 调用代理函数,传递一个右值int(临时对象)  
    proxyFunction(100); // 输出: Target function called with value: 100  

    return 0;
}

上面代码的输出为:

Target function called with value: 12
Target function called with value: 100
1
2
在这个示例中,proxyFunction 是一个模板函数,它接受一个参数 value,该参数的类型由模板参数 T 推导得出。通过使用 std::forward<T>(value),确保了 value 的原始值类别(左值或右值)在传递给 targetFunction 时保持不变。

当 lvalue(一个左值 int)被传递给 proxyFunction 时,由于使用了完美转发,targetFunction 接收到的仍然是一个左值引用。同样地,当传递一个右值(如字面量 100)时,targetFunction 接收到的也是一个右值引用(尽管在这个特定的例子中 targetFunction 的参数是一个值类型,不是引用类型,因此右值会被转换为左值。在实际应用中,targetFunction 可能会接受一个引用类型的参数以利用右值引用的优势,比如进行移动操作)。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/h8062651/article/details/136920285

9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999

target forward function

C++ 新特性 | C++ 11 | std::forward、万能引用与完美转发

爱吃红萝卜的小白兔

已于 2024-01-24 23:05:27 修改

阅读量2.5k
 收藏 24

点赞数 16
分类专栏: C++ 新特性 文章标签: c++ java 开发语言
版权

C++ 新特性
专栏收录该内容
12 篇文章1 订阅
订阅专栏
文章目录
一、std::forward、万能引用与完美转发
1、万能引用
2、类型推导
3、引用折叠
4、std::forward

概述
std::forward是C++11中引入的一个函数模板,用于实现完美转发。它的作用是根据传入的参数,决定将参数以左值引用还是右值引用的方式进行转发。

传统上,当一个左值传递给一个函数时,参数会以左值引用的方式进行传递;当一个右值传递给一个函数时,参数会以右值引用的方式进行传递。完美转发是为了解决传递参数时的临时对象(右值)被强制转换为左值的问题。std::forward实现完美转发主要用于以下场景:提高模板函数参数传递过程的转发效率。

一、std::forward、万能引用与完美转发
1、万能引用
对于形如T&&的变量或者参数,如果T可以进行推导,那么T&&称之为万能引用。换句话说,对于形如T&&的类型来说,其既可以绑定左值,又可以绑定右值,而这个的前提是T需要进行推导。最常见的万能引用方式如以下两种:

函数模板:

template<typename T>
void f(T&& param); // 存在类型推导,param是一个万能引用
1
2
auto类型推导:

auto&& var = var1; // 存在类型推导,var是一个万能引用
1
注意:只有当发生自动类型推断时(例如:函数模板的类型自动推导),T &&才是万能引用。下面一个示例中的&& 并不是一个万能引用,例如:

template<typename T>
void f( T&& param); // 这里T的类型需要推导,所以&&是一个 universal references

template<typename T>
class Test {
    Test(Test&& rhs);  // Test是一个特定的类型,不需要类型推导,所以&&表示右值引用  
};

2、类型推导
万能引用进行类型推导时需要推导出T&&中的T的真实类型:若传入的参数是一个左值,则T会被推导为左值引用;而如果传入的参数是一个右值,则T会被推导为原生类型(非引用类型)。

对于万能引用来说,条件之一就是类型推导,但是类型推导是万能引用的必要非充分条件,也就是说参数必须被声明为T&&形式不一定是万能引用。示例如下:

template<typename T>
void func(std::vector<T>&& t); // t是右值引用
1
2
调用func时会执行类型推导,但是参数t的类型声明的形式并非T &&而是std::vector &&。 之前强调过,万能引用必须是T &&才行,因此,t是一个右值引用,如果尝试将左值传入,编译器将会报错:

std::vector<int> v;
fun(v); // 编译错误,不能将左值绑定到右值
1
2
形如const T&&的方式也不是万能引用:

template<typename T>
void f(const T&& t); // t是右值引用

int main() {
  int a = 0;
  f(a); // 错误
}

3、引用折叠
引用折叠是一种特性,允许在模板元编程中使用引用类型的参数来创建新的引用类型。由于存在T&&这种万能引用类型,当它作为参数时,有可能被一个左值/左值引用或右值/右值引用的参数初始化,这需要通过类型推导,推导后得到的参数类型会发生类型变化,这种变化就称为引用折叠。

根本原因是因为C++中禁止reference to reference,所以编译器需要对四种情况(& &、& &&,&& &,&& &&)进行处理,将他们折叠成一种单一的reference。引用折叠的规则如下:如果两个引用中至少其中一个引用是左值引用,那么折叠结果就是左值引用;否则折叠结果就是右值引用。示例如下:

using T = int &;
T& r1;  // int& & r1 -> int& r1
T&& r2; // int& && r2 -> int& r2
  
using U = int &&;
U& r3;  // int&& & r3 -> int& r3
U&& r4; // int&& && r4 -> int&& r4

下面是一个具体的示例,可以看下对应的推导过程

template<typename T>
void func(T &&t) {
    cout << "hello world" << endl;
}

int main() {
    int a = 1;
    int &b = a;
    func(a); // T 推导成 int &; T && ==> int & && ==> int &
    func(b); // T 推导成 int &; T && ==> int & && ==> int &
    func(1); // T 推导成 int; T && ==> int &&
    func(std::move(a)); // T 推导成 int &&; T && ==> int && && ==> int &&
    return 0;
}

4、std::forward
完美转发是为了解决传递参数时的临时对象(右值)被强制转换为左值的问题,std::forward源码如下:

template<class T>
T&& forward(typename std::remove_reference<T>::type& t) noexcept {
  return static_cast<T&&>(t);
}

template <class T>
T&& forward(typename std::remove_reference<T>::type&& t) noexcept {
  return static_cast<T&&>(t);
}


其内部实现只有一行代码,即static_cast<T&&>(t)使用static_cast<>进行类型转换,与std::move()实现方式类似。结合前面介绍的引用折叠,当接收一个左值作为参数时,std::forward<>()返回左值引用,相应的,当接收一个右值作为参数时,std::forward<>()返回右值引用。

版本一:没有实现完美转发

下面给出一个案例没有实现完美转发,如下:

#include <iostream>

template <typename T>
void wrapper(T u) {
    fun(u);
}

class MyClass {};

void fun(MyClass& a) { std::cout << "in fun(MyClass&)\n"; }
void fun(const MyClass& a) { std::cout << "in fun(const MyClass&)\n"; }
void fun(MyClass&& a) { std::cout << "in fun(MyClass &&)\n"; }

int main(void) {
    MyClass a;
    const MyClass b;

    fun(a);
    fun(b);
    fun(MyClass());

    std::cout << "----- Wrapper ------\n";
    wrapper(a);
    wrapper(b);
    wrapper(MyClass());

    return 0;
}

输出结果:

in func(MyClass&)
in func(const MyClass&)
in func(MyClass &&)
----- Wrapper ------
in func(MyClass&)
in func(const MyClass&)
in func(const MyClass&)

Process returned 0 (0x0)   execution time : 0.253 s
Press any key to continue.

最后一行函数调用结果不符合预期,传入的是MyClass &&右值引用,预期调用fun(MyClass&& a),实际上调用的却是fun(const MyClass& a)。调用wrapper函数时触发拷贝构造,基于右值创建了左值u(即:wrapper函数的参数),u的实际类型是const MyClass,匹配的是fun(const MyClass& a)

版本二:使用std::forward实现完美转发

使用万能引用和完美转发来修改前面的例子,如下:

#include <iostream>

template <typename T>
void wrapper(T &&u) { // 万能引用
    func(std::forward<T>(u)); // 完美转发
}

class MyClass {};

void func(MyClass& a) { std::cout << "in func(MyClass&)\n"; }
void func(const MyClass& a) { std::cout << "in func(const MyClass&)\n"; }
void func(MyClass&& a) { std::cout << "in func(MyClass &&)\n"; }

int main(void) {
    MyClass a;
    const MyClass b;

    func(a);
    func(b);
    func(MyClass());

    std::cout << "----- Wrapper ------\n";
    wrapper(a);
    wrapper(b);
    wrapper(MyClass());

    return 0;
}

输出结果:

in func(MyClass&)
in func(const MyClass&)
in func(MyClass &&)
----- Wrapper ------
in func(MyClass&)
in func(const MyClass&)
in func(MyClass &&)

Process returned 0 (0x0)   execution time : 0.210 s
Press any key to continue.

输出结果符合预期,使用std::forward实现了完美转发。

注意:std::forward()建议仅用于模板函数,对于非模板的,因为不涉及到类型推导,所以使用完美转发是没有意义的。

https://mp.weixin.qq.com/s/bT–OIA7X8Uohxl_q1O98A
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/cloud323/article/details/135824374

88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值