何时选择重载,何时用universal引用?

摘要:本文翻译自《effective modern C++》,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢! 博客已经迁移到 "这里啦" Item
本文翻译自《effective modern C++》,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢! 博客已经迁移到这里啦 Item 26已经解释了,不管是对全局函数还是成员函数(尤其是构造函数)而言,对universal引用的重载会导致一系列的问题。到目前为止,我也已经给出了好几个例子,如果它能表现得和我们期待的一样,这种重载也能很实用。此Item会探索如何让这种重载能实现我们所需求的行为。我们可以设计出避免对universal引用进行重载的实现,也可以通过限制参数的类型,来使得它们能够匹配。 我们的讨论将继续建立在Item 26介绍的例子上。如果你最近没有读过那个Item,你需要在继续此Item前复习一下它。 抛弃重载 Item 26中的第一个例子(logAndAdd)就是一个典型的例子,很多这样的函数如果想要避免对universal引用进行重载,那只要简单地对即将重载的函数进行不同的命名即可。举个例子,两个logAndAdd重载能被分割成logAndAddName和logAndAddNameIdx。可惜的是,这个方法不能在第二个例子(Person构造函数)中工作,因为构造函数的名字是由语言固定的。再说了,谁又想放弃重载呢? 通过const T&传参数 另一个选择是回到C++98,并且把pass-by-universal-reference(通过universal引用传参数)替换成pass-by-lvalue-reference-to-const(通过const左值引用传参数)。事实上,这是Item 26考虑的第一个方法(显示在175页)。这个办法的缺点是它的效率无法达到最优。要知道,对于我们现在所知道的universal引用和重载来说,牺牲一些效率来保持事情的简单性可能是一个很有吸引力的方案。 传值 一个常常能让你提升效率并且不增加复杂性的办法是把传引用的参数替换成传值的参数。虽然这很不直观,但这个设计遵守了Item 41的建议(当知道你需要拷贝一个对象时,直接通过传值来传递它)。所以,对于它们怎么工作以及它们有多高效的细节部分,我会推迟到Item 41再讨论。在这,我只是给你看一下这个技术怎么用在Person例子中去: class Person { public: explicit Person(std::string n) // 替换T&&构造函数对于 : name(std::move(n)) {} // std::move的使用请看Item 41 explicit Person(int idx) // 和之前一样 : name(nameFromIdx(idx)) {} ... private: std::string name; }; 因为std::string的构造函数接受类型为整型的参数,所以所有传给Person构造函数的int及类int(比如,std::size_t, short, long)的参数讲调用int版本的重载。相似的,所有的std::string类型(以及那些可以用来创建一个std::string的参数,比如字符串"Ruth")会被传给以std::string为参数的构造函数。因此对于调用者来说,这里没有意外发生。你能争论说“我觉得有些人还是会感到奇怪,他们使用0或NULL来代表null指针,所以这会掉用int版本的重载”,但是这些人应该回到Item 8,然后再读一次,直到他们觉得使用0或NULL来表示null指针会让他们觉得可怕。 使用Tag dispatch(标签分发) 不管是通过lvalue-reference-to-const传递还是传值的方式来支持完美转发。如果使用universal引用的动机是完美转发的话,我们没有其他的选择。我们还是不想抛弃重载。所以如果我们不想抛弃重载,也不想抛弃universal引用的话,我们怎么才能避免对universal引用进行重载呢? 事实上没有这么困难。重载函数的调用是这样的:依次查看每个重载函数的参数(形参)以及调用点的参数(实参),然后选择最匹配的重载函数(匹配上所有的形参和实参)。一个universal引用参数通常提供一个格外的匹配,使得不管传入的是什么,都能匹配上,但是如果universal引用只是参数列表的一部分,这个参数列表还包含其他不是universal引用的参数,那么,即使不考虑universal引用,非universal引用参数就足够我们造成不匹配了。这就是tag dispatch方法背后的基础,一个例子会让之前的描述更加好理解。 我们把tag dispatch永在logAndAdd177页的例子上去。
阅读全文