前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >终于弄明白了万能引用和右值引用的区别

终于弄明白了万能引用和右值引用的区别

作者头像
用户9831583
发布2022-12-04 16:07:10
1.8K0
发布2022-12-04 16:07:10
举报
文章被收录于专栏:码出名企路

第5章 右值引用,移动语义和完美转发

/**

几个概念:

1,移动语义:使用移动操作替换复制操作,比如移动构造函数和移动赋值运算符替换复制构造函数和复制赋值运算符

移动语义使得创建只移动型别对象成为可能,这些型别包括 std::unique_ptr std::future和std::thread等

2,完美转发:使人们可以撰写接受任意实参的函数模板,并转发到其他函数,目标函数会接受到与转发函数所接受的完全相同的实参

3,右值引用:将1,2 联系起来的底层语言机制,使 1,2成为可能

*/

条款23:理解std::move和std::forward

/**

std::move 并不进行任何移动,仅仅只执行强制型别转换,无条件地将实参强制转换成右值

std::forward也不进行任何转发,仅仅在特定条件满足时才执行同一个强制转换

两者在运行期间都无所作为,也不会生成任何可执行代码,连一个字节都不会生成

*/

//C++11 std::move的实现

代码语言:javascript
复制
//C++11 std::move的实现
template<typename T>
typename remove_reference<T>::type&& move(T&& param)
{
    using ReturnType = typename remove_reference<T>::type&&;
    
    //std::move 实质部分,强制型别转换
    //其形参使指涉到一个对象的引用,返回的使指涉到同一个对象的引用
    //就是将实参强制转换成了右值
    return static_cast<ReturnType>(param);
}

//C++14 std::move简明扼要的方式实现

代码语言:javascript
复制
//C++14 std::move简明扼要的方式实现
template<typename T>
decltype(auto) move(T&& param)
{
    using  ReturnType = remove_reference_t<T>&&;
    return static_cast<ReturnType>(param);
}

//看一个例子: 形参复制到一个数据成员

//为了避免付出将 text复制入数据成员的过程产生的复制操作成本, 对 text实施了 std::move,从而产生了一个右值

代码语言:javascript
复制
class Annotation{
    public:
        explicit Annotation(const stringStr text): value(std::move(text)){

        }
        void printVaule();
    private:
        stringStr value;
};
void Annotation:: printVaule()
{
    std::cout<<"value: "<<value.str<<std::endl;
}

/**

真的如题说得那样嘛?

实际上:text并非使被移动,他还是被复制入 value 得,text 已经被 std::move强制转换成为一个右值

但是,text是被声明为 const std::string得,在强制转换之前,是个左值 const std::string , 而强制转换得结果是个右值 const std::string

再看一下 string得构造函数

代码语言:javascript
复制
class stringStr{
    public:
    
      stringStr(const string& rhs){
           std::cout<<"copy "<<std::endl;
        }//复制构造
      stringStr(string&& rhs){
            std::cout<<"move "<<std::endl;
        }//移动构造  

        std::string str = "liyushu";
    /**
    可以看出:因为 std::move(text)得结果是个 const std::string型别得右值
    1,这个右值无法传递给 std::string得移动构造函数,因为移动构造函数只能接受非常量 std::string型别得右值引用作为形参
    2,这个右值可以传递给复制构造函数,因为指涉到常量得左值引用允许绑定到一个常量右值型别得形参
    */
};

/**

以上得到两个结:

1,如果想取得对某个对象执行移动操作得能力,则不用将其声明为常量,因为针对常量执行得移动操作会变成复制操作

2,std::move 不仅不实际移动任何东西,甚至不保证经过其强制型别转换后得对象具备可移动得能力,只是其结果是个右值

*/

//std::forward :特定条件下才实施强制型别转换,分场景

//场景1:某个函数模板取用了万能引用型别为形参,随后将其传递给另一个函数

代码语言:javascript
复制
//场景1:某个函数模板取用了万能引用型别为形参,随后将其传递给另一个函数
class Widget{

};
void process(const Widget& lval){
    std::cout<<"left: "<<std::endl;
    }//处理左值
void process(Widget&& rval){
    std::cout<<"right: "<<std::endl;
    }//处理右值
template<typename T>
void logAndProcess(T&& param)
{
    //把 param传递给process得函数模板
    process(std::forward<T>(param));
    /**
    期望的是: 传入的是左值,该左值可以调用 process的左值函数,右值也一样

    但是:所有函数形参都是左值,因此,logAndProcess内对process的调用都会是取用了左值型别的那个重载版本

    因此:std::forward的作用就出来了:当传入右值时候,把param强制转换成右值,可以调用 process的右值版本
    
    */
}

//场景2:不推荐 std::forward 而推荐 std::move

//一个类,用它进行移动构造函数的调用次数的跟踪

//使用std::move实现

代码语言:javascript
复制
//使用std::move实现
class WidgetA{
    public:
        WidgetA(){}
        WidgetA(WidgetA&& rhs): s(std::move(rhs.s)){
            ++moveC;
        }
   // private:
        static std::size_t moveC;
        std::string s;
};
std::size_t  WidgetA::moveC = 0;

//使用std::forward实现

代码语言:javascript
复制
//使用std::forward实现
class WidgetB{
    public:
        WidgetB(){}
        WidgetB(WidgetB&& rhs): s(std::forward<std::string>(rhs.s)){
            ++moveC;
        }
    private:
        static std::size_t moveC;
        std::string s; 
};
std::size_t  WidgetB::moveC = 0;

客户端测试

代码语言:javascript
复制
int main()
{   
    //测试1
    std::string str = "liyy";
    Annotation *ann = new Annotation(str);
    ann->printVaule();

    //测试2
    Widget w;
    //调用时传入左值
    logAndProcess(w);
    //调用时候传入右值
    logAndProcess(std::move(w));

    //测试3:
    WidgetA wA;
    WidgetA wAA(std::move(wA));
    WidgetA wAAA(std::move(wAA));
    std::cout<<wAAA.moveC<<std::endl;

    WidgetB wB;
    WidgetB wBB(std::move(wB));
    WidgetB wBBB(std::move(wBB));
    std::cout<<wAAA.moveC<<std::endl;

}

copy value:liyushu left: right: 2 2

/**

结论:

1,std::move实施的是无条件的向右值型别的强制型别转换,本身而言,不会执行移动操作

2,std::foreard仅仅对绑定到右值得引用实施向右值型别得强制转换,仅仅传递一个对象到另一个函数,保持原始对象得型别

3,运行期间,两者都不做任何操作

*/

条款24:区分万能引用和右值引用

/**

回到一个问题:遇到 T&& 时,一定是右值引用嘛?

两种含义:

1, 右值引用,仅仅会绑定到右值,识别出可移动对象

2,万能引用,可以是左值引用 T&,也可以是右值引用, 也可以绑定到 const对象或 volatile对象或非两者对象

*/

//右值引用:不涉及型别推导

代码语言:javascript
复制
//右值引用:不涉及型别推导
class Widget{

};
void f(Widget&& param);
Widget&& vara1 = Widget();

//万能引用:涉及型别推导,必要非充分条件

代码语言:javascript
复制
//1,函数模板的形参
template<typename T>
void f(T&& param);

//2, auto声明
auto&& var2 = var1;

//万能引用:首先是个引用,初始化是必需的,万能引用的初始化物会决定它代表的是个左值还是右值引用

//1,如果初始化是右值,万能引用就会对应到一个右值引用

//2,如果初始化物是左值,万能引用就会对应到一个左值引用

代码语言:javascript
复制
Widget w;
f(w);//左值被传递给f , param的型别是 Widgwt& 左值引用
f(std::move(w));//右值被传递给f, param的型别是 Widget&& 右值引用

//万能引用:扩展,以下情况不是

//1

//必须是 T&& 的形式,形如这样

代码语言:javascript
复制
//1
//必须是 T&& 的形式,形如这样
template<typename T>
void f(std::vector<T>&& param);
//f被调用时,param的型别声明的形式不是 T&& , 而是std::vector<T>&& 这就排除了 param是个万能引用的可能性,param是右值引用
//因此,如下是错误的,一个右值引用不能绑定一个左值
std::vector<int> v;
f(v);

//2

//const修饰,也不是万能引用

代码语言:javascript
复制
//2
//const修饰,也不是万能引用
template<typename T>
void f(const T&& param);

//3

//即使是 T&& 位于模板内,并不能保证一定涉及型别推导

代码语言:javascript
复制
//3
//即使是 T&& 位于模板内,并不能保证一定涉及型别推导
template<class T,class Allocator = allocator<T>>
class vector{
    public:
        void push_back(T&& x);
};
//形如 T&& , 但是不涉及型别推导,因为 push_back作为 vector的一部分,如不存在特定的 vector实例,则它也不存在
//该实例的具现完全决定了 push_back的声明型别,给定:
std::vector<Widget> v;
//会导致 std::vector模板具现化为如下实例
template<Widget,allocator<Widget>>
class vector{
    public:
        void push_back(Widget&& x);
};

//3-3: 作为3的对比,emplace_back却存在型别推导

代码语言:javascript
复制
//3-3: 作为3的对比,emplace_back却存在型别推导
template<class T,class Allocator = allocator<T>>
class vector{
    public:
        template <class... Args>
        void emplace_back(Args&&... args);
};
//型别形参 Args独立于 vector的型别形参 T,所以 Args必须在每次 emplace_back被调用时进行推导
//名字是 Args,不是T,但仍然是个万能引用,必要非充分条件:T&& ,但不一定取名 T
//又如:
template<typename MyTemplateType>
void func(MyTemplateType&& param);

//4, auto&& 型别的变量都是万能引用

//计算任意函数调用所花费的时长

代码语言:javascript
复制
//4, auto&& 型别的变量都是万能引用
//计算任意函数调用所花费的时长
auto timeFuncInvocation = [](auto&& func, auto&&... params)
{
    //开始计时
    std::forward<decltype(func)>(func)(
        std::forward<decltype(params)>(params)...
    );
    //计时器结束
};

// 如果函数模板形参具 T&& 型别,并且 的型别系推导而来,或如果

// 使用 auto&& 声明其型别,则该形参或对象就是个万能引用

// • 如果型别声明并不精确地具各 type&& 的形式,或者型别推导并未发生,则

// type&& 就代表右值引用

// • 若采用右值来初始化万能引用,就会得到一个右值引用 若采用左值来初

// 始化万能引用,就会得到一个左值引用

条款25:指针右值引用实施std::move,针对万能引用实施 std::forward

//1

//右值引用:std::move

//会绑定到可移动的对象上,绑定的对象可移动

//方法:把绑定到了这些对象的形参转换成右值

代码语言:javascript
复制
class Widget{
    public:
        Widget(){}
        Widget(Widget&& rhs):name(std::move(rhs.name)), p(std::move(rhs.p)){
            //rhs是右值引用
        }

    //private:
        std::string name;
        std::shared_ptr<int> p;

};

//2

//万能引用:std::forward

//只是可能绑定到可移动的对象上

代码语言:javascript
复制
class WidgetAA{
    public:
        WidgetAA(){}

        template<typename T>
        void setName(T&& newName){
            name = std::forward<T>(newName);
        }

    //private:
        std::string name;
        std::shared_ptr<int> p;
};

//3

//错误写法

//以上两者不能颠倒混用:比如针对万能引用使用 std::move,会意外改动值

代码语言:javascript
复制
class WidgetBB{
    public:
        WidgetBB(){}

        template<typename T>
        void setName(T&& newName){
            name = std::move<T>(newName);
        }

    //private:
        std::string name;
        std::shared_ptr<int> p;
};
std::string getWidgetName()
{
    return "liyushu,liyushu";
}

//4

//初级程序员会这样改进:

//重载setName为常量左值和右值实现不同的重载

代码语言:javascript
复制
class WidgetBBB{
    public:
        WidgetBBB(){}
        
        //从常量左值 取得赋值
        void setName(const std::string& newName){
            name = newName;
        }
        //从右值 取得赋值
        void setName(std::string&& newName){
            name = std::move(newName);
        }

    //private:
        std::string name;
        std::shared_ptr<int> p;
};

//以上改进的缺点:1,需要编写和维护更多代码 2,效率打折扣,3, 可扩展性差

/**

重点解释 2 效率的问题:

1, 如改进为 std::forward,n 被传递给 setName,然后再转手传递给 w内部的 std::string的赋值运算符

W的数据成员name可以直接从字符串字面值得到赋值,而不会产生std::string型别的临时对象

2,重载版本的 setName得到创建 std::string型别的临时对象以供其形参绑定,随后该临时对象才会移入 w的

数据成员,因此,重载版本做了如下事情:一次 std::string的构造函数来创建临时对象,一次std::string的

移动赋值运算符来移动 newName到 w.name,还有一次std::string的析构函数来销毁临时变量

解释3:扩展问题

一个形参可以重载,两个,多个形参呢?你给我重载看看?比如下面的实现

*/

//5

//常用的多参数重载版本

代码语言:javascript
复制
template<class T,class... Args>//C++11
shared_ptr<T> make_shared(Args&&... args);

template<class T,class... Args>//c++14
unique_ptr<T> make_unique(Args&&... args);

//6

//某些情况,想要再单一函数内将谋个对象不止一次地绑定到右值引用或万能引用,而且想保证完成对该对象地其他所有操作之前

//其值不被移走,在这种情况下,你就得仅在最后一次使用该引用时候,对其实施 std::move或 std::forward

代码语言:javascript
复制
// template<typename T>
// void setSignText(T&& text)
// {
//     //text是个万能引用

//     //使用text但不改动其值
//     sign.setText(text);

//     //text地值不会被 setText修改,需要调用add时候使用该值,仅仅在万能引用最后一次使用

//     //有条件地将 text强制转换成右值型别
//     signHistory.add(std::forward<T>(text));
// }

//7

//在按值返回地函数中,如果返回地是绑定到一个右值引用或一个万能引用地对象,

//则当你返回该引用时,应该对其实施 std::move或者std::forward

代码语言:javascript
复制
// class Matrix{
//     public:

//         //重载+操作
//         friend Matrix operator+=(Matrix&& lhs, const Matrix& rhs);
// };

// //以下编译有 bug
// Matrix operator+=(Matrix&& lhs, const Matrix& rhs)
// {   
//     //常量左值引用既可以操作左值,也可以操作右值 rhs
//     //右值引用必须立即进行初始化操作,且只能使用右值进行初始化
//     lhs += rhs;
//     return std::move(lhs);//lhs移入返回值

//     // return lhs;//lhs复制入返回值,没有移动快
// }

//8

//std::forward也类似,如果原始对象是一个右值,它的值应当被移动到返回值上,这样可以避免制造副本的成本

//但如果原始对象是一个左值,那就必须创建出实实在在的副本

代码语言:javascript
复制
class Fraction{

};
class Reduce{
    public:
       void reduce(){
         std::cout<<"I am liyushu! "<<std::endl;
       }
};
template<typename T>
T reduceAndCopy(T&& frac)
{
    frac.reduce();
    return std::forward<T>(frac);
    //对于右值 是移入返回值
    //对于左值 是复制入返回值
    //如果省去 std::forward的调用,则 frac会无条件地复制到 函数 返回值中
}

//https://www.cnblogs.com/wangpei0522/p/4472548.html

//9

//局部变量地返回:不能用 std::move或std::forward (复制转为移动,没门!!!!!!)

代码语言:javascript
复制
Widget makeWidget()
{
    Widget w;
    w.name = "love liyushu";

    //return std::move(w);//错误!
    return w;//正确?
    //这里返回地不是局部对象w,  而是w的引用,std::move(w)的结果
}

/**

编译器如要在一个按值返回地函数里省略对局部对象地复制或移动,需要满足两个条件:

1,局部对象型别和函数返回值型别相同

2,返回地就是局部对象本身

因此上述代码可以改为:return w;

*/

//https://blog.csdn.net/Jacky_Feng/article/details/120742414

客户端测试:

代码语言:javascript
复制
int main()
{
    //测试1:
    Widget wa;
    wa.p = std::shared_ptr<int>(new int(1));
    wa.name = "lys";
    Widget wb(std::move(wa));
    std::cout<<wb.name<<" "<<wb.p<<std::endl;

    //测试2:
    WidgetAA waa;
    std::string&& str = "liyushu";//右值
    std::string str1 = "liyushu";//左值 都OK
    waa.setName(str);
    std::cout<<" "<< waa.name <<std::endl;

    //测试3:
    WidgetBB wbb;
    auto n = getWidgetName();
    std::cout<<"wbb: "<< wbb.name <<" n:"<< n <<std::endl;//1
    wbb.setName(n);
    std::cout<<"wbb: "<< wbb.name <<" n:"<< n <<std::endl;//2
    /**
    说明:1 n输出正常,2 n输出异常
    因为:  n是个局部变量,setName函数内部使用了 std::move把它的引用形参无条件地强制转换到右值
    n的值被移动到 w.name,这样一来,调用完 setName函数返回时,n将变成一个不确定的值
    改进:
        std::move改为 std::forward
    */
    
    //测试4:
    WidgetBBB wbbb;
    auto nb = getWidgetName();
    std::cout<<"wbbb: "<< wbbb.name <<" nb: "<< nb <<std::endl;//1
    wbbb.setName(nb);
    std::cout<<"wbbb: "<< wbbb.name <<" nb: "<< nb <<std::endl;//
    
    //测试7:
    // Matrix a,b;
    // Matrix c = std::move(a) + b;

    //测试8:
    Reduce re;
    Reduce  fa = reduceAndCopy(std::move(re));

    //测试9”
    Widget www =  makeWidget();
    std::cout<<"WWW: "<<www.name<<std::endl;
 
}

lys 0x55acd2355eb0 liyushu wbb: n:liyushu,liyushu wbb: liyushu,liyushu n: wbbb: nb: liyushu,liyushu wbbb: liyushu,liyushu nb: liyushu,liyushu I am liyushu! WWW: love liyushu

// 1, 针对右值引用的最后一次使用实施 std: :move, 针对万能引用的最后一次使

// 用实施 std:: forward

// 2, 作为按值返回的函数的右值引用和万能引用,依上一条所述采取相同行为

// 3, 若局部对象可能适用于返回值优化 则请勿针对其实施 std: :move 或 std::forward

条款26:避免万能引用型别进行重载

//假设这样一种情况:

//取用一个名字作为形参,记录当下时间和日期,再把该名字添加到一个全局数据结构

//打印函数

代码语言:javascript
复制
template<class T>
void Print(T first, T last)
{
    for(; first != last; ++first)
        std::cout<< *first <<" ";
    std::cout<<std::endl;
}
template<class T>
void log(T time, const std::string& name)
{

}

//实现1:

代码语言:javascript
复制
//实现1:
std::multiset<std::string> names;
void logAndAdd(const std::string& name)
{
    auto now = std::chrono::system_clock::now();

    log(now,"logAndAdd");
    names.emplace(name);
}
//实现1无可厚非,但是考虑效率却不行

//实现2:改进1中复制带来的效率问题

代码语言:javascript
复制
//实现2: 改进1中复制带来的效率问题
template<typename T>
void logAndAdd(T&& name)
{
    auto now = std::chrono::system_clock::now();

    uint64_t dis_millseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count()
  - std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count() * 1000;
 time_t tt = std::chrono::system_clock::to_time_t(now);
 auto time_tm = localtime(&tt);
 char strTime[25] = { 0 };
 sprintf(strTime, "%d-%02d-%02d %02d:%02d:%02d %03d", time_tm->tm_year + 1900,
  time_tm->tm_mon + 1, time_tm->tm_mday, time_tm->tm_hour,
  time_tm->tm_min, time_tm->tm_sec, (int)dis_millseconds);
    
 std::cout << strTime << std::endl;

    log(now,"logAndAdd");
    names.emplace(std::forward<T>(name));
}

//实现3:

//重载版本 int

//返回索引对应的名字

代码语言:javascript
复制
//实现3:
//重载版本 int
//返回索引对应的名字
std::string nameFromIdx(int idx);
void logAndAdd(int idx)
{
    auto now = std::chrono::system_clock::now();
    std::cout<<"int: "<<std::endl;
    log(now,"logAndAdd");
    //names.emplace(nameFromIdx(idx));
}

//结论

//形参为万能引用的函数,在具现过程中,和几乎任何实参型别都会产生精确匹配,一旦万能引用成为重载候选

//它就会吸引大批的实参型别

//实现4:

//如何解决:撰写一个带完美转发的构造函数

代码语言:javascript
复制
//实现4:
//如何解决:撰写一个带完美转发的构造函数
class Person{
    public:
        //1,完美转发构造函数初始化了数据成员
        template<typename T>
        explicit Person(T&& n): name(std::forward<T>(n)){
            std::cout<<"forward: "<<std::endl;
        }
        //2, 形参为int的构造函数
        explicit Person(int idx): name(nameFromIdx(idx)){
            std::cout<<"int: "<<std::endl;
        }
        //3, 编译器生成 复制构造函数
        Person(const Person& rhs){
            std::cout<<"copy: "<<std::endl;
        }
        //4, 编译器生成 移动构造函数
        Person(Person&& rhs){
            std::cout<<"move: "<<std::endl;
        }

    private:
        std::string name;
};

//因此,不要用万能引用作为重载版本

客户端测试:

代码语言:javascript
复制
int main()
{
    //测试1:
    logAndAdd("liyushu");
    Print(names.begin(),names.end());

    std::string petName("lishushu");
    //传递左值std::string
    //name是个左值,它是被复制入 names的,没人任何办法避免这个复制操作,因为传递给 logAndAdd的是个左值 petName
    logAndAdd(petName);

    //传递右值  std::string
    //形参ame 绑定到了一个右值,name自身是个左值,所以它是被复制入 names的,这个调用中,付出一次复制的成本,可以用
    //一次移动达到同样目标
    logAndAdd(std::string("liyushushu"));

    //传递字符串
    //形参name还是绑定到一个右值,但这次这个 std::string型别的临时对象是从 “liyangyyyy”隐式构造的,name是被复制入names的
    logAndAdd("liyangyyyy");

    //测试2:
    //将左值复制入 multiset
    logAndAdd(petName);
    //对右值实施移动而非复制
    logAndAdd(std::string("love liyushu"));
    //在multiset中直接构造一个 std::string对象,而非复制一个 std::string型别的临时对象
    logAndAdd("shushushu");

    //测试3:
    //调用重载版本
    logAndAdd(22);
    //如下发生错误
    short nameIdx;
    //logAndAdd(nameIdx);//错误!
    /**
     logAndAdd有两个重载版本
     1, 形参型别为 T&& 的版本可以将T推导为short,从而产生一个精确匹配
     2,形参型别为 int的版本只能在型别提升以后才能匹配到 short型别的实参

     精确匹配优先于提升以后才能匹配,所以,形参型别为万能引用的版本才是被调用的版本

     为什么short传给万能引用却报错呢?

     1,调用执行后,形参name被绑定到传入的 short型别的变量上
     2, name被std::forward传递给 names的emplace成员函数
     3, name又被转发给 std::string的构造函数,但是 std::string的构造函数中并没有形参为 short的版本
         
    */
     
     //测试4:
     Person p("nacy");
     //Person pp(p);
     /**
     调用的是 forward版本
     非常量左值 p 被初始化,模板构造函数可以实例化来接受 Person型别的非常量左值形参
     */
    const Person cp("nacy");
    Person cpp(cp);
}

条款27:熟悉依万能引用型别进行重载的替代方案

//方法1: 传值

//提升性能的方法:把传递的形参引用型别替换成值型别

//当你知道肯定需要赋值形参时,考虑按值传递对象

代码语言:javascript
复制
std::string nameFromIdx[4] = {"liyushu","lishushu","lxiaoshu","liyuyu"};
class Person{
    public:
        //替换掉T&& 型别的构造函数
        explicit Person(std::string n):name(std::move(n)){
            std::cout<<"string: "<<std::endl;
        }
        explicit Person(int idx):name(nameFromIdx[idx]){
            std::cout<<"int: "<<std::endl;
        }
    private:
        std::string name;
};

//方法2:标签分配

//不想舍弃重载,又不想舍弃万能引用,该怎么办呢?

//如果万能引用仅是形参列表的一部分,该列表中还有其他非万能引用型别的形参的话,那么只要该非万能引用形参

//具备充分差的匹配能力,则它就足以将这个带有万能引用形参的重载版本踢出局

//改造 e26 中的函数

代码语言:javascript
复制
template<class T>
void Print(T first, T last)
{
    for(; first != last; ++first)
        std::cout<< *first <<" ";
    std::cout<<std::endl;
}
template<class T>
void log(T time, const std::string& name)
{

}
std::multiset<std::string> names;
template<typename T>
void logAndAdd(T&& name)
{
    auto now = std::chrono::system_clock::now();

    log(now,"logAndAdd");
    names.emplace(std::forward<T>(name));
}
//如 e2 一样,若出现重载 int 的版本,会出现调用问题

//改进2:

//重新实现 logAndAdd,把它委托两个函数,一个接受整型值,另一个接受其他所有型别

//而 logAndAdd 本身则接受所有型别的实参,无论整型还是非整型

代码语言:javascript
复制
template<typename T>
void logAndAddImpl(T&& name,std::false_type)//非整型实参,才进行下面步骤
{
    auto now = std::chrono::system_clock::now();

    log(now,"logAndAdd");
    std::cout<<"logAndAddImpl: false"<<std::endl;
    //names.emplace(std::forward<T>(name));
}

template<typename T>
void logAndAddImpl(T&& name,std::true_type)//非整型实参,才进行下面步骤
{
    auto now = std::chrono::system_clock::now();

    log(now,"logAndAdd");
    std::cout<<"logAndAddImpl: true"<<std::endl;
    //names.emplace(std::forward<T>(name));
}

//改进2-1

代码语言:javascript
复制
template<typename T>
void logAndAdd21(T&& a)
{   
    cout<<boolalpha;
    cout<<is_integral<T>()<<endl;

    logAndAddImpl(std::forward<T>(a),is_integral<T>());
    //第二个参数用来检测传入的是否是整型
    //但是,如果传给万能引用name的实参是个左值,那么 T 就被被推导为左值引用,所以,如果传递
    //给 logAndAdd的是个左值 int,则 T 就会被推导 为 int&。这不是个整型,因为引用型别都不是整型
    //所以,std::is_intergral<T> 在函数接受了任意左值实参时候,会得到结果 假
    

    //改进2-2:
    //std::remove_reference 正如其名,也正如所需,它会移除型别所附的一切引用修饰词
    logAndAddImpl(std::forward<T>(a),std::is_integral<typename std::remove_reference<T>::type>());// C++11
    //logAndAddImpl(std::forward<T>(a),std::is_integral<typename std::remove_reference_t<T>);//C++14
}

客户端测试

代码语言:javascript
复制
int main()
{
    //测试1:
    Person pp(12.0);

    //测试2-1:
    int a=1;
    int b =10;
    logAndAdd21(a);//传递左值 false
    logAndAdd21(10);//传递右值 true

}

int: false logAndAddImpl: false logAndAddImpl: true true logAndAddImpl: true logAndAddImpl: true

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码出名企路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第5章 右值引用,移动语义和完美转发
    • 条款23:理解std::move和std::forward
      • 条款24:区分万能引用和右值引用
        • 条款25:指针右值引用实施std::move,针对万能引用实施 std::forward
          • 条款26:避免万能引用型别进行重载
            • 条款27:熟悉依万能引用型别进行重载的替代方案
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档