Item 26: Avoid overloading on universal reference (Effective Modern C++ 读书笔记)

Item 26: Avoid overloading on universal reference

之前看书看到了25,决定写读书笔记,所以从26开始,全书完本后回补之前的。写笔记感觉比只看书效果好很多,虽然用了更多时间,但是确实非常有收获。开始正题。

上例子:

std::multiset<std::string> names;

void logAndAdd(const std::string& name)
{
	auto now = std::chrono::system_clock::now();
	log(now, “logAndAdd”);
	names.emplace(name);
}

std::string petName(“Darla”);
logAndAdd(petName);
logAndAdd(std::string(“Persephone”));
logAndAdd(“Patty Dog”);

第一个调用,参数name被绑在petName上,因为name(传递petName)是左值,所以最终被复制到了names里面。
第二个调用,参数name被绑定在右值上,但是因为name本身是左值,所以复制进names
第三个调用,参数绑定右值,是一个临时字符串,相似,复制进names
以上函数用左值绑定,所以无论输入左值或者右值,最终只能进行复制,效率低。因此创建一个模板函数来接受综合引用,如下:

template<typename T>
void logAndAdd(T&& name)
{	
	auto now = std::chrono::system_clock::now();
	log(now, "logAndAdd");
	names.emplace(std::forward<T>(name));
}

std::string petName("Darla");
logAndAdd(petName);
logAndAdd(std::string("Persephone"));
logAndAdd("Patty Dog");

再来看三次调用,第一个参数推导为左值引用,复制进names,同上。
第二个参数推导为右值,movenames
第三个直接在names里面建立字符串。

模板化以后,第二和第三个调用效率提高。

问题1:
如果使用者想用indexlogAndAdd进行调用,如下:

std::string nameFromIdx(int idx);

返回一个名字。
对函数进行重载:

void logAndAdd(int idx)
{	
	auto now = std::chrono::system_clock::now();
	log(now, "logAndAdd");
	names.emplace( nameFromIdx(idx));
}

使用方法如下:

std::string petName("Darla");
logAndAdd(petName);
logAndAdd(std::string("Persephone"));
logAndAdd("Patty Dog");
logAndAdd(22);

问题来了,函数接受一个int型,按理说,如果你使用一个整数相关类型,如short型,调用接受int参数的重载函数最合适,但是

short nameIdx;
logAndAdd(nameIdx);

会报错,看似以int为参数的重载更适合这次调用,但是实际上,这次调用用的是模板综合引用的重载函数的调用。
原文解释是:Functions taking universal references are the greediest function in C++.
理解过来就是,除非有安全适合的函数,否则,综合引用的函数的优先级最高。所以short类型的输入,会被综合引用的重载函数推导为short左值引用。最终强行将short类型往stringmultiset中塞,必然报错。

问题2:
构造函数重载的例子

class Person{
public:
    template<typename T>
    explicit Person(T&& n)
      :name(std::forward<T>(n)){}

    explicit Person(int idx)
      :name(nameFromIdx(idx)){}
   …
private:
   std::string name;
}

和之前的例子一样,如果使用short创建对象,那么必然调用综合引用构造函数。
系统会自动生成copymove构造函数:

Person(const Person& rhs);
Person(Person&& rhs);

在构造对象时
Person p(“Nancy”);这个没问题,调用综合引用重载构造函数
auto cloneOfP(p);这里就没法编译。
原因是本应该调用copy构造函数的这里,实际上优先选择了完美转发构造函数(即模板综合引用构造函数),为啥嘞?因为只有const Person& 类型的参数才最适合系统自动生成的copy构造函数,和前一个例子一个道理,没有完全适合的,就优先使用综合引用进行推导。
所以如果想调用copy构造函数咋办?这样:

const Person cp("Nancy");
auto cloneOfP(cp);

还有一个特例就是,假如模板构造函数同样能把输入的参数推导成和copy构造函数一样的参数咋办?
explicit Person(const Person& n);假设这个是推导出来的构造函数
Person(const Person& rhs);这个是系统copy构造函数。
这个时候会优先调用后者。调用规则就是如果模板和普通函数都match,优先使用普通函数。

问题3:

class SpecialPerson : public Person{
public:
   SpecialPerson(const SpecialPerson& rhs)
   :  Person(rhs)
   {}

   SpecialPerson(const SpecialPerson&& rhs)
   :  Person(std::move(rhs))
   {}
};

作为派生类的copymove构造函数,理想上,调用基函数的copymove函数最合适,但是实际上调用的却是完美转发构造函数。为啥嘞这又是? 原因是,派生类传递到基类的参数是const SpecialPerson& rhs 而不是const Person& rhs,理由如问题2,调用了模板函数。

总结

1. 避免重载综合引用。
2. C++的调用优先级为:参数完全match的普通函数,然后就是综合引用推导函数,所以想用相近的参数(例如用short,long类型的参数调用int函数)调用普通函数,过不了推导函数这一关。

另:水平有限,望读过此文发现有问题的不吝指教,感激。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值