按值传递还是按引用传递

使用std::ref和std::cref

        从 C++11 开始,可以让调用者自行决定向函数模板传递参数的方式。如果模板参数被声明成 按值传递的,调用者可以使用定义在头文件<functional>中的 std::ref()和std::cref()将参数按引用传递给函数模板,比如:

#include <functional>

template <typename T>
void square(T a) { a *= a; }

int main(int argc, char **argv)
{
    double a = 1.414;
    square(std::ref(a));
}

        上面的例子似乎不足以体现std::ref或std::cref的重要性,再来看一例子:

#include <functional>
#include <algorithm>
#include <vector>

template <typename T>
void add(T &raw, T &sum) {
    sum += raw;
    raw += 1;
}

int main(int argc, char **argv) {
    int sum = 0;
    std::vector<int> v1 {1, 2, 3, 4, 5};
    std::for_each(v1.begin(), v1.end(), std::bind(add<int>, std::placeholders::_1, sum));
    return 0;
}

        执行过程中,会发现sum并没有按引用传递,而是按值传递。很明显,这不符合预期。要解决这个问题,调用std::for_each时,使用std::ref(sum)替换sum即可,如下:

...
std::for_each(v1.begin(), v1.end(), std::bind(add<int>, std::placeholders::_1, std::ref(sum)));
...

        但使用std::ref时要注意一个问题,其返回的是reference_wrapper,而是不是原始参数类型,因此,下面的代码是无法通过编译的: 

//...
template <typename T>
void sub(T a, T b, T r) { r = a - b; }
//...
    double x = 10.1;
    double y = 3.5;
    double z = 0.0;
    //note: candidate template ignored: deduced conflicting types for parameter 'T' ('double' vs. 'reference_wrapper<double>')
    sub(x, y, std::ref(z));
//...

处理字符串常量和裸数组

         对于字符串常量和裸数组做模板参数:

  • 按值传递,参数类型退化成指针
  • 按引用传递,参数类型不退化,是指向数组的引用。

        因此,对于下面的源码无法编译通过:

//...
template <typename T>
int compare(const T &lhs, const T &rhs) { return strcmp(lhs, rhs); }

int main(int argc, char **argv)
{
    compare("hello", "abc");
    return 0;
}
//...

        "hello"和"abc"分别被推导成char[6],和char[4],编译器找不到合适的函数模板,详细错误如下:

error: no matching function for call to 'compare'
    compare("hello", "abc");

note: candidate template ignored: deduced conflicting types for parameter 'T' ('char[6]' vs. 'char[4]')
int compare(const T &lhs, const T &rhs) { return strcmp(lhs, rhs); }

        上面的问题可以通过函数重载解决,重载的方法有多种,但普通函数重载最为简单,也更直接明了。如下:

//通用性太差,无法区分数组变量和普通变量
template <typename T>
int compare(T lhs, T rhs) { return strcmp(lhs, rhs); }

//过于繁琐
template <typename T, size_t L1, size_t L2>
int compare(T (&lhs)[L1], T (&rhs)[L2]) { return strcmp(lhs, rhs); }

int compare(const char *lhs, const char *rhs) { return strcmp(lhs, rhs); }

        总体看来,还是普通函数重载更为简洁。

处理返回值

        返回值既可以是值,也可以是引用。以下三种场景,函数通常会返回引用:

  • 需要直接修改返回的对象
  • 提升效率,重新生成一个对象代价较高,如size比较大的容器
  • 为链式调用返回一个对象,如std::string的+操作符,重载<<,>>操作符  

        但在某些情况下,可能更希望函数按值返回。对于普通函数,要做到这点很容易,但对于函数模板,要做到这点,就比较困难。如下面的例子,函数返回的便是引用:

#include <string>
#include <iostream>

template <typename T>
T retval(T val)  { return T{val}; }

int main(int argc, char **argv)
{
    std::string str0 = "hello";
    std::cout << std::is_same<std::string, decltype(retval<std::string &>(str0))>::value << std::endl;
    std::cout << std::is_same<std::string, std::remove_reference<decltype(retval<std::string &>(str0))>::type>::value << std::endl;
    return 0;
}

        如果想函数按值返回,有三种解决方案:

  • 使用std::remove_reference推导返回值类型去掉引用修饰
template <typename T>
typename std::remove_reference<T>::type retval(T val)  { return T{val}; }
  • 使用std::decay推导返回值类型去掉引用修饰
template <typename T>
typename std::decay<T>::type retval(T val)  { return T{val}; }
  • 使用auto推导返回值类型去掉引用修饰。不过该方案直到c++14之后,才能实现去掉引用修饰的目的;c++11要实现这一目的,比较复杂,因为c++11使用auto需要后跟类型推导。实现如下:
//c++11实现返回值无法去掉引用
template <typename T>
auto retval(T val) -> decltype(val)  { return T{val}; }

//c++14实现返回值已去掉引用
template <typename T>
auto retval(T val) { return T{val}; }
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值