zoukankan      html  css  js  c++  java
  • 上篇博客的解答与模板的应用

    之前的一篇博客中已经提到过了, 使用模板的目的是提高效率, 可是会因为用户输入的不可预知性导致计划中的函数没有匹配到, 而是被模板函数接收, 所以我们的策略就是, 使用 SFINAE 这个 trick:

    template<typename T>
    void LogAndAdd(T &&name)
    {
        LogAndAddImpl
        (std::forward<T>(name)
        , std::is_integral<T>());
    }

    可是问题来了, 传入的引用是一个左值, 所以推断出的 T 就是那个左值的引用类型(如果传入一个 int, 那么推断出的 T 就是 int&) 所以揭开传入参数的本来面目, 就要把引用去掉, 对于这个需求, 简单暴力的模板函数:

    remove_reference<T>

    简单暴力到看名字就知道这是什么了, 是这么用的:

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

    然而, 这又引出一个问题: is_integral<T>() 的值是在运行时才能判断的, 而使用模板的目的就是想要把在运行时处理的问题提前到编译时期, 所以解决的方式是使用 tags, 我个人觉得有点 switch case 的神韵:

    //两种情况
    template<typename T>
    void LogAndAddImpl(T &&name, std::flase_type)
    {
        auto now = chrono::system_clock::now();
        log(now, "logAndAdd");
        names.emplace(std::forward<T>(name));
    }
    
    std::string NameFromIdx(int idx, std::true_type)
    {
        void LogAndAdd(NameFromIdx(idx));
    }

    好, LogAndAdd 问题已经基本被比较满意的解决了, 下面则是对于类 Person 构造函数的问题.
    对于这个问题我们要使用一个 trick, 叫做 enable_if<conditon>:type, 大概意思就是, 只有在满足 conditon 的条件下 , 其中的 type 才会被产生:

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

    enable_if 其实又是另一个 trick 的一个运用, 这个 trick 叫做 SFINAE, 在此也不细说.
    然后问题来了, 这既然是一个 拷贝/移动 构造函数的模板, 那么怎么判断拷贝的是不是一个 Person 呢?
    这里又有一个模板类, std::is_same<T, U>, 其中, std::is_same<T, U>::type 的类型是 bool, 用于表示 T 和 U 是否是同一类型. 这样, 问题就解决了......吗? 并不是, 还需要考虑到两个问题:
      1. Person, Person& 和 Person&& 可不是一个类型
      2. 是否有 const 和 volatile 的前缀又是一个问题
    如此多的干扰, 总不能把这些情况的排列组合写一遍吧?! 幸好这里又有一个使参数返璞归真的模板类 std::decay<T>, 它的 type 就是 T 的本来面目. 剔除了干扰, 就方便我们的判断了:

    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);
        ...
    };

    终于完成了! 我简直想要撒h......需求又变了, Person 出于某种原因, 需要一个子类: SpecialPerson.

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

    看起来倒是正确的, 可是, Person 和 SpecialPerson 可不是一样的, 即使是在 decay 之后(果然够 special), 直接放进去必然会出错. 那么这该如何是好呢?
    所以说 C++11 大法好, 居然还有亲子鉴定模板类 std::is_base_of<T, U>, 用于判断 U 是否继承 T. std::is_base_of<T, U>::value 也是一个 bool 类型的值, 所以我们只要把之前的代码稍加修改:

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

    哈哈哈! 终于完成了, 只是有一点点不爽: 中间又是 type 又是 value 的, 一不小心就乱了(我初学, 容易弄乱) 所以对于 C++11, 还是 C++14 大法更好:

    //C++14
    class Person{
    public:
        tempalte<
            typename T
            , typename = std::enable_if_t<
                            !std::is_base_of<Person
                                            , std::decay_t<T>
                                        >::value
                         >
            >
            explicit Person(T &&n);
            ...
    };

    我突然发现边看书边做笔记的坏处, 就是容易被书牵着鼻子走, 我都不知道已经是第几遍说:"这样就完成了!" 结果呢? 尼玛居然还是没完!
    为啥捏? 因为假如用户给咱们的模板函数传入一个既不是 Person, 也不是 SpecialPerson 还不是 整数的参数, 这个函数会如何处理呢? 哈, 当然是当作整数来处理了......这能行? 所以要再加一道锁链:

    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)
            :_name(std::forward<T>(n))
        {...}
        
        explicit Person(int idx)
            :_name(NameFromIdx(idx))
        {...}
        ...
    };

    到此大功告成, 无论传进模板化构造函数的参数是左值还是右值, 都会得到最佳的处理, 无论是 有着 const 还是 volatile 都会被正确的处理, 传入的整数也不会被错误的被卷入模板. 我们仅用了区区这几行代码就一举处理了如此多的情况, 证明之前的讨论是值得的.
    最后的最后, 其实还是有一个问题, 这个设计是正确的, 问题是 forward<T> 本身有缺陷
      1. forward 这个过程并不完美, 在以后细说.
      2. 错误信息不明确, 这个说明一下:
    举个例子, 有个用户传进来一个 char16_t 的数组:

    Person(u"Konrad Zuse"); //character of type fucking const char16_t

    模板遇到这个奇葩就会给出一个错误信息......嗯, 最多大概能有 160 行, 中间路过的层数越多, 给的错误信息就越多.
    咋办呢? 既然要检查, 就别让错误走远, 一开始就来个断言, 并自己给一个说人话的错误信息, 此外也把运行时的错误检查提前到编译期, 提升性能.

    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>>::value
            >
        >
        explicit Person(T &&n)
            :_name(forward<T>(n))
        {
            static_assert(
                std::is_constrctible<std::string, T>::value
                , "Parameter n cannot be used to construct a std::string"
            );
            ...
        }
        ...
    };
  • 相关阅读:
    上传图片,将图片保存在腾讯云(2种方式)
    由ping所引发的思考~
    php面试上机题(2018-3-3)
    【八】jqeury之click事件[添加及删除数据]
    【七】jquery之属性attr、 removeAttr、prop[全选全不选及反选]
    【六】jquery之HTML代码/文本/值[下拉列表框、多选框、单选框的选中]
    【五】jquery之事件(focus事件与blur事件)[提示语的出现及消失时机]
    小白懂算法之基数排序
    mysql_sql199语法介绍
    Python基本编程快速入门
  • 原文地址:https://www.cnblogs.com/wuOverflow/p/4208729.html
Copyright © 2011-2022 走看看