IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> Item 27: Familiarize yourself with alternatives to overloading on universal references. -> 正文阅读

[C++知识库]Item 27: Familiarize yourself with alternatives to overloading on universal references.

Item 27: Familiarize yourself with alternatives to overloading on universal references.

Effective Modern C++ Item 27 的学习和解读。

Item26 中建议大家尽量不要对万能引用进行重载,但同时也确实存在需要对万能引用进行重载的场景。今天就和大家探索下如何满足这种场景的需求,这个 Item 将沿用上个 Item的例子,阅读本文前建议先看上一个 Item。

放弃重载

对于 Item26 中 logAndAdd 函数,为了避免万能引用实例化匹配产生的问题,一种方式就是不使用重载,取而代之的是给这些重载函数起不同的名字。

logAndAddName(...)
logAndAddNameByIdx(...)

这种方法虽然在一定程度上可以解决这个问题,但是对于构造函数,就无能为力了(构造函数函数名是固定的)。

const T& 传递

另一种选择是不采用万能引用传参(pass-by-universal-reference),使用 const T& 传参。这也是 Item26 开始就介绍的方法,但是它的缺点是效率不高。

值传递

直接选择传值,这种方式将在 Item41 中继续讨论,这里只介绍这种方法的使用:

class Person {
public:
  explicit Person(std::string n) // replaces T&& ctor; see
  : name(std::move(n)) {}        // Item 41 for use of std::move
  explicit Person(int idx)       // as before
  : name(nameFromIdx(idx)) {}private:
  std::string name;
};

std::string 的构造函数没有传 int 的版本,所有 int 类型和类似 int 类型(int,short,size_t,long)参数的传递给构造函数都将匹配到 int 类型重载构造函数。类似的,所有 std::string 类型和类似 std::string 类型(比如字面的"Ruth")的参数传递给构造函数都将匹配到 std::string 类型重载构造函数。

使用 Tag 分发

const T& 传递和值传递都不支持完美转发。如果使用万能引用的动机是为了完美转发,那还必须只能使用万能引用,并且那你也不想放弃重载,这里介绍一种使用 Tag 分发的方法。

基于 Tag 分发其实就是使用 Tag 对参数进行区分,进而分发到不同的函数实现。对于上个 Item 中的例子:

std::multiset<std::string> names; // global data structure
template<typename T>              // make log entry and add

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

使用 Tag 分发的实现如下:

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

void logAndAddImpl(int idx, std::true_type)
{
  logAndAdd(nameFromIdx(idx)); // up name and
} 

template<typename T>
void logAndAdd(T&& name)
{
  logAndAddImpl(
    std::forward<T>(name),
    std::is_integral<typename std::remove_reference<T>::type>()
  );
}

C++11 引入的 std::is_integral<T> 可以判断参数类型是否为整形。在这个例子中,如果 logAndAdd 传入的是左值类型的 int,T将被推导成 int&,但不是 int,为了解决这个问题,使用 std::remove_reference 去除引用。

从概念上讲,logAndAdd 传递了一个布尔值给 logAndAddImpl,表示传递的实参是否为整形。但是我们知道 true 和 false 都是运行时的值,而模板匹配是编译阶段的事情。C++ 标准库提供了 std::true_type 和 std::false_type 两种类型代表 true 和 false 的含义。如果 T 是整形,那么 logAndAdd 传递给 logAndAddImpl 的参数是一个继承了 std::true_type 的对象,否则是一个继承了std::false_type 的对象。

约束接受万能引用的模板

使用 Tag 分发的技术,是在通用引用参数函数内部根据参数类型进行分发,它解决不了 Item26 中介绍的 Person 完美转发构造函数的问题。如果你在一个构造函数内部实现 Tag 分发,但是编译器在一些情况下会自动生成构造函数,将会绕过使用 Tag 分发的构造函数。

问题不在于编译器生成的构造函数会绕过使用 Tag 分发的完美转发构造函数,而是从来没有绕过。例如你想用一个左值的对象去初始化一个新的对象,你想调用的是编译器生成的拷贝构造函数,但是正如 Item26 介绍的那样,实际上调用完美转发的构造函数。

万能引用的匹配重载函数总是贪婪的,我们需要另外一种技术控制万能引用调用的条件,那就是 std::enable_if。

默认条件下,所有模板都是 enable 的,当使用了 std::enable_if 后,只有满足条件的模板才是 enable 的。语法规则是这样的:

class Person {
  public:
    template<typename T,
             typename = typename std::enable_if<condition>::type> 
    explicit Person(T&& n);
    .....
};

只有满足了 condition 条件才使能。我们期望不是 Person 类型的参数,模板构造函数才使能,当然我们可以使用 is_same 来判断类型是否相同,因而我们的条件可能是 !std::is_same<Person, T>::value。但是这里会有点小问题,比如 Person 和 Person& 不是一个类型,而我们这里显然不希望 Person& 类型满足条件进而使能模板。

  • 对于引用。我们期望 Person& 和 Person&& 都像 Person 一样处理,即不使能模板。
  • 对于 const 和 volatile,即 CV 描述符。我们期望 const Person、volatile Person 和 volatile const Person 也能像 Person 一样处理,即不使能模板。

标准库为我们提供了 std::decay,std::decay<T>::type 的类型 和 T 的类型相同,它忽略了引用和 CV 描述符。因此我们想控制模板使能的条件是:

!std::is_same<Person, typename std::decay<T>::type>::value

这样就可以得到我们想要的实现:

class Person {
public:
  template<
    typename T,
    typename = typename std::enable_if<
                 !std::is_same<Person,
                               typename std::decay<T>::type
                               >::value
               >::type
  >
  explicit Person(T&& n);};

再看 Item26 中万能引用重载在遇到类继承的问题:

class SpecialPerson: public Person {
public:
  SpecialPerson(const SpecialPerson& rhs) // copy ctor; calls
  : Person(rhs)                           // base class
  {}                                   // forwarding ctor!
  SpecialPerson(SpecialPerson&& rhs)      // move ctor; calls
  : Person(std::move(rhs))                // base class
  {}                                   // forwarding ctor!
};

当我们拷贝或移动一个 SpecialPerson 对象,我们期望调用基类的拷贝或移动构造函数,但是我们传递给基类的是 SpecialPerson 类型的参数,会匹配到基类的完美转发构造函数。

标准库 type trait 提供了 std::is_base_of 帮我们解决这个问题。如果 T2 继承于 T1,那么 std::is_base_of<T1, T2>::value 为 true,并且 std::is_base_of<T, T>::value 也是 true。上面的代码使用 std::is_base_of 代替 is_same 得到的代码将更加合适:

class Person {
public:
  template<
    typename T,
    typename = typename std::enable_if<
                 !std::is_base_of<Person,
                                  typename std::decay<T>::type
                                  >::value
               >::type
  >
  explicit Person(T&& n);};

如果使用 C++14 实现将更加简洁:

class Person {
public:
  template<
    typename T,
    typename = typename std::enable_if_t<
                 !std::is_base_of<Person,
                                  std::decay_t<T>::type
                                  >::value
               >
  >
  explicit Person(T&& n);};

到目前为止,我们已经接近了完美解决了 Item26 中介绍的万能引用模板重载的问题。再加上处理整数参数类型的 Person 的重载,我们汇总代码如下:

class Person {
public:
  template<
    typename T,
    typename = std::enable_if_t<
      !std::is_base_of<Person, std::decay_t<T>>::value
      &&
      !std::is_integral<std::remove_reference_t<T>>::value
    >
  >
  explicit Person(T&& n)     // ctor for std::strings and
  : name(std::forward<T>(n)) // args convertible to
  {}                      // std::strings
  explicit Person(int idx)   // ctor for integral args
  : name(nameFromIdx(idx))
  {}// copy and move ctors, etc.
private:
  std::string name;
};

权衡

本 Item 介绍的后两种技术:使用 Tag 分发和限制模板使能条件,都支持了完美转发。但使用完美转发也有缺点:

一个是有些类型不能完美转发,这个将在 Item30 中讨论。另外一个是当用户传递无效参数时,编译报错信息的可读性非常差。

例如,在创建 Person 对象的时候传递了个char16_t(C++11引进的一种以16位表示一个字符的类型)字符组成的字符串,而不是char:

Person p(u"Konrad Zuse"); // "Konrad Zuse" consists of characters of type const char16_t

当使用本 Item 的前三种技术时,编译器看到可执行的构造函数只接受 int 和 std::string,编译器会产生一些直观的错误信息表明:无法将 const char16_t[12] 转换到 int 或 std::string。

万能引用在接受 char16_t 类型时候没有问题,当构造函数把 char16_t 类型数组转发到 std::string 成员变量的构造中时,才发现 char16_t 数组不是 std::string 可接受的参数类型,我使用 g++ 的报错信息如下:

hello.cpp: In instantiation of ‘Person::Person(T&&) [with T = const char16_t (&)[12]; <template-parameter-1-2> = void]’:
hello.cpp:30:26:   required from here
hello.cpp:18:28: error: no matching function for call to ‘std::__cxx11::basic_string<char>::basic_string(const char16_t [12]): name(std::forward<T>(n)) // args convertible to
                            ^
In file included from /usr/include/c++/7/string:52:0,
                 from /usr/include/c++/7/bits/locale_classes.h:40,
                 from /usr/include/c++/7/bits/ios_base.h:41,
                 from /usr/include/c++/7/ios:42,
                 from /usr/include/c++/7/ostream:38,
                 from /usr/include/c++/7/iostream:39,
                 from hello.cpp:1:
/usr/include/c++/7/bits/basic_string.h:604:9: note: candidate: template<class _InputIterator, class> std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(_InputIterator, _InputIterator, const _Alloc&)
         basic_string(_InputIterator __beg, _InputIterator __end,
         ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:604:9: note:   template argument deduction/substitution failed:
hello.cpp:18:28: note:   candidate expects 3 arguments, 1 provided
   : name(std::forward<T>(n)) // args convertible to
                            ^
In file included from /usr/include/c++/7/string:52:0,
                 from /usr/include/c++/7/bits/locale_classes.h:40,
                 from /usr/include/c++/7/bits/ios_base.h:41,
                 from /usr/include/c++/7/ios:42,
                 from /usr/include/c++/7/ostream:38,
                 from /usr/include/c++/7/iostream:39,
                 from hello.cpp:1:
/usr/include/c++/7/bits/basic_string.h:566:7: note: candidate: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&&, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
       basic_string(basic_string&& __str, const _Alloc& __a)
       ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:566:7: note:   candidate expects 2 arguments, 1 provided
/usr/include/c++/7/bits/basic_string.h:562:7: note: candidate: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
       basic_string(const basic_string& __str, const _Alloc& __a)
       ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:562:7: note:   candidate expects 2 arguments, 1 provided
/usr/include/c++/7/bits/basic_string.h:558:7: note: candidate: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(std::initializer_list<_Tp>, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
       basic_string(initializer_list<_CharT> __l, const _Alloc& __a = _Alloc())
       ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:558:7: note:   no known conversion for argument 1 from ‘const char16_t [12]’ to
std::initializer_list<char>’
/usr/include/c++/7/bits/basic_string.h:531:7: note: candidate: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
       basic_string(basic_string&& __str) noexcept
       ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:531:7: note:   no known conversion for argument 1 from ‘const char16_t [12]’ to
std::__cxx11::basic_string<char>&&’
/usr/include/c++/7/bits/basic_string.h:519:7: note: candidate: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type, _CharT, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type = long unsigned int]
       basic_string(size_type __n, _CharT __c, const _Alloc& __a = _Alloc())
       ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:519:7: note:   candidate expects 3 arguments, 1 provided
/usr/include/c++/7/bits/basic_string.h:509:7: note: candidate: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
       basic_string(const _CharT* __s, const _Alloc& __a = _Alloc())
       ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:509:7: note:   no known conversion for argument 1 from ‘const char16_t [12]’ to
const char*’
/usr/include/c++/7/bits/basic_string.h:499:7: note: candidate: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type = long unsigned int]
       basic_string(const _CharT* __s, size_type __n,
       ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:499:7: note:   candidate expects 3 arguments, 1 provided
/usr/include/c++/7/bits/basic_string.h:481:7: note: candidate: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&, std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type, std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type = long unsigned int]
       basic_string(const basic_string& __str, size_type __pos,
       ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:481:7: note:   candidate expects 4 arguments, 1 provided
/usr/include/c++/7/bits/basic_string.h:465:7: note: candidate: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&, std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type, std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type = long unsigned int]
       basic_string(const basic_string& __str, size_type __pos,
       ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:465:7: note:   candidate expects 3 arguments, 1 provided
/usr/include/c++/7/bits/basic_string.h:450:7: note: candidate: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&, std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type = long unsigned int]
       basic_string(const basic_string& __str, size_type __pos,
       ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:450:7: note:   candidate expects 3 arguments, 1 provided
/usr/include/c++/7/bits/basic_string.h:437:7: note: candidate: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
       basic_string(const basic_string& __str)
       ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:437:7: note:   no known conversion for argument 1 from ‘const char16_t [12]’ to
const std::__cxx11::basic_string<char>&’
/usr/include/c++/7/bits/basic_string.h:429:7: note: candidate: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
       basic_string(const _Alloc& __a) _GLIBCXX_NOEXCEPT
       ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:429:7: note:   no known conversion for argument 1 from ‘const char16_t [12]’ to
const std::allocator<char>&’
/usr/include/c++/7/bits/basic_string.h:420:7: note: candidate: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string() [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]
       basic_string()
       ^~~~~~~~~~~~
/usr/include/c++/7/bits/basic_string.h:420:7: note:   candidate expects 0 arguments, 1 provided

如果完美转发多次,错误信息将更加迷惑。std::is_constructible 可以在编译期间测试一个类型的对象是否能被另一个不同类型(或一些不同类型)的对象(或者另一些对象)构造,我们可以使用 static_assert 断言来实现:

class Person {
public:
  template<
    typename T,
    typename = std::enable_if_t<
      !std::is_base_of<Person, std::decay_t<T>>::value
      &&
      !std::is_integral<std::remove_reference_t<T>>::value
    >
  >
  explicit Person(T&& n)     // ctor for std::strings and
  : name(std::forward<T>(n)) // args convertible to std::strings
  {
    // assert that a std::string can be created from a T object
	static_assert(
	  std::is_constructible<std::string, T>::value,
	  "Parameter n can't be used to construct a std::string"
	);}                      
  explicit Person(int idx)   // ctor for integral args
  : name(nameFromIdx(idx))
  {}// copy and move ctors, etc.
private:
  std::string name;
};

g++ 编译报错如下:

// ...... 此处省略
/usr/include/c++/7/bits/basic_string.h:420:7: note:   candidate expects 0 arguments, 1 provided
hello.cpp:20:6: error: static assertion failed: Parameter n can't be used to construct a std::string
      static_assert(
      ^~~~~~~~~~~~~

至此,我们我们已经完美解决了 Item26 中介绍的万能引用模板重载的问题。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 17:22:53  更:2022-04-18 17:23:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:23:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码