移动语义和enable_if<>

完美转发

        c++11引入了一个新的模板函数——std::forward,用于实现完美转发,即强制参数以右值形式转发。为什么要引入这样一个模板?先来看一个例子:

#include <iostream>

class X {
};

void f(X &x) {
    std::cout << "call f(X &x)" << std::endl;
}

void f(const X &x) {
    std::cout << "call f(const X &x)" << std::endl;
}

void f(X &&x) {
    std::cout << "call f(X &&x)" << std::endl;
}

void g(X &x) {
    f(x);
}

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

void g(X &&x) {
    f(x);
}

int main(int argc, char **argv) {
    X x;
    const X cx;
    g(x); //call f(X &x)
    g(cx); //call f(const X &x)
    g(X()); //call f(X &x)
    g(std::move(x)); //call f(X &x)
}

        当执行g(X())和g(std::move(x))时,g(X &&x)理想地情况下是调用f(X &&x),但实际上却调用了f(X &x)。之所以会这样,是因为C++编译过程中发生了引用折叠(关于这一概念,可以参考《C++ 新特性 | C++ 11 | std::forward、万能引用与完美转发》)。通过使用std::forward,可以消除引用折叠,提高参数传递效率,如下:

void g(X &&x) {
    f(std::forward<X>(x));
}

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

        std::forward的实现

template <typename T>
struct remove_reference {
    typedef T type;
};

template <typename T>
struct remove_reference<T &> {
    typedef T type;
};

template <typename T>
struct remove_reference<T &&> {
    typedef T type;
};

template <typename T>
using remove_reference_t = typename remove_reference<T>::type;

template <typename T>
T &&forward(remove_reference_t<T> &x) {
    return static_cast<T &&>(x);
}

template <typename T>
T &&forward(remove_reference_t<T> &&x) {
    return static_cast<T &&>(x);
}

        关于std::forward的几点问题:

  • remove_reference_t就是一组特化模板,通过forward的应用来看,仅有第一个定义就可以了,后面两个完全多余,但要知道remove_reference_t并非仅为forward而生,看下面的例子:
X x;
auto &&x__ = x;
std::cout << std::is_same_v<decltype(x__), X> << std::endl; //0
std::cout << std::is_same_v<remove_reference_t<decltype(x__)>, X> << std::endl; //1
  • forword强制参数以右值方式转发,例如下面的实现g(T &x)将会调用f(T &&x),所以对于forward不要乱用:
template<typename T>
void h(T &x) {
    f(forward<T>(x));
}

引入enable_if 表达式

        类的特殊成员函数也可以是模板,例如构造函数,但有时这可能会带来令人意外的结果,如下面的例子:

#include <iostream>
#include <utility>
#include <string>

class Person final {
public:
    template <typename T>
    explicit Person(T &&name) : m_PersonName(std::forward<T>(name)) {
        std::cout << "TMPL-CONSTR for ’" << m_PersonName << "’\n";
    }
    
    Person (const Person &other) : m_PersonName(other.m_PersonName) {
        std::cout<<"COPY-CONSTRPerson’" << m_PersonName << "’\n";
    }
    
    Person (Person &&other) : m_PersonName(std::move(other.m_PersonName)) {
        std::cout << "MOVE-CONSTR Person ’" << m_PersonName << "’\n";
    }
    
private:
    std::string m_PersonName;
};


int main(int argc, char **argv)
{
    std::string s = "sname";
    Person p1(s);
    Person p2("tmp");
    Person p3(p1); //ERROR: In instantiation of function template specialization 'Person::Person<Person &>' requested here

    Person p4(std::move(p1));
    
    return 0;
}

        上面的源码看上去没有任何错误,但却无法通过编译,错误如下: 

        很明显Person p3(p1)没有按常理出牌——调用拷贝构造函数,而是调用了成员模板template<typename STR> Person(STR&& n)。之所以会这样,是因为成员模板的模板参数T被替换成Person &,变成Person(Persion &n),是一个比预定义拷贝构造函数更匹配的函数。试图解决这个问题,需要引入一种新的工具enable_if,新的源码如下:

//...
class Person final {
public:
    template <typename T, typename U = typename std::enable_if<std::is_convertible<T, std::string>::value>::type>
    explicit Person(T &&name) : m_PersonName(std::forward<T>(name)) {
        std::cout << "TMPL-CONSTR for ’" << m_PersonName << "’\n";
    }
    //...
};
//...

        如果T可以被转换成std::string,这个定义会被扩展成template<typename STR, typename = void> Person(STR&& n);,否则,这个模板会被忽略。

简化enable_if表达式

  • 使用using重命名

//...
template <typename T>
using enable_str_temlate = typename std::enable_if<std::is_convertible<T, std::string>::value>::type;

class Person final {
public:
    template <typename T, typename U = enable_str_temlate<T>>
    explicit Person(T &&name) : m_PersonName(std::forward<T>(name)) {
        std::cout << "TMPL-CONSTR for ’" << m_PersonName << "’\n";
    }
    //...
};
//...
  • 使用requires(但支持该语法至少要到C++20)
//...
class Person final {
public:
    template <typename T>
    requires std::is_convertible<T, std::string>::value
    explicit Person(T &&name) : m_PersonName(std::forward<T>(name)) {
        std::cout << "TMPL-CONSTR for ’" << m_PersonName << "’\n";
    }
    //...
};
//...
  • 使用concept(但支持该语法至少要到C++20)
//...
template <typename T>
concept concept_str_template = std::is_convertible<T, std::string>::value;

class Person final {
public:
    template <typename T>
    requires concept_str_template<T>
    explicit Person(T &&name) : m_PersonName(std::forward<T>(name)) {
        std::cout << "TMPL-CONSTR for ’" << m_PersonName << "’\n";
    }
    //...
};
//...

  • 17
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值