zoukankan      html  css  js  c++  java
  • 第6章 移动语义和enable_if:6.2 特殊成员函数模板

    6.2 Special Member Function Templates

    6.2 特殊成员函数模板

    Member function templates can also be used as special member functions, including as a constructor, which, however, might lead to surprising behavior.

    特殊成员函数也可以是模板,例如构造函数。但是这可能会导致令人奇怪的行为。

    Consider the following example:

    考虑下面的例子:

    #include <utility>
    #include <string>
    #include <iostream>
    
    class Person
    {
    private:
        std::string name;
    public:
        // constructor for passed initial name:
        explicit Person(std::string const& n) : name(n) {
            std::cout << "copying string-CONSTR for '" << name << "'
    ";
        }
    
        explicit Person(std::string&& n) : name(std::move(n)) {
            std::cout << "moving string-CONSTR for '" << name << "'
    ";
        }
    
        // copy and move constructor:
        Person(Person const& p) : name(p.name) {
            std::cout << "COPY-CONSTR Person '" << name << "'
    ";
        }
    
        Person(Person&& p)  noexcept : name(std::move(p.name)) {
            std::cout << "MOVE-CONSTR Person '" << name << "'
    ";
        }
    };
    
    int main() {
        std::string s = "sname";
        Person p1(s); //用string对象初始化 => 调用Person(const string&)
        Person p2("tmp"); //用字符串字面量初始化 => 调用移动构造函数:Person(string&& n)
        //Person p3(p1); //拷贝Person对象 => 调用Person(const Person&)
        Person p4(std::move(p1)); //移动Person对象 => 调用移动构造函数Person(Person&&)
    }

    Here, we have a class Person with a string member name for which we provide initializing constructors. To support move semantics, we overload the constructor taking a std::string:

    上例中,Person类有一个string类型的name成员和几个初始化构造函数。为了支持移动语义,我们重载了接受std::string参数的构造函数。

    • We provide a version for string object the caller still needs, for which name is initialized by a copy of the passed argument:

     提供一个以string对象为参数的构造函数,并用其副本来初始化name成员。

    Person(std::string const& n) : name(n) {
         std::cout << "copying string-CONSTR for ’" << name << "";
    }

    • We provide a version for movable string object, for which we call std::move() to “steal” the value from:

      提供一个以可移动string对象为参数的构造函数,并通过std::move()从中窃取值来初始化name成员:

    Person(std::string&& n) : name(std::move(n)) {
        std::cout << "moving string-CONSTR for ’" << name << "";
    }

    As expected, the first is called for passed string objects that are in use (lvalues), while the latter is called for movable objects (rvalues):

    与预期的一样,传递左值的string对象参数会调用第1个构造函数。而传递可移动对象(右值)则会调用第2个构造函数:

    std::string s = "sname";
    Person p1(s); // init with string object => calls copying string-CONSTR
    Person p2("tmp"); // init with string literal => calls moving string-CONSTR

    Besides these constructors, the example has specific implementations for the copy and move constructor to see when a Person as a whole is copied/moved:

    除了这些构造函数之外,本例中还提供了一个拷贝构造函数和移动构造函数。以查看当传入Person对象时,何时是被复制,何时被移动的。

    Person p3(p1); // copy Person => calls COPY-CONSTR
    Person p4(std::move(p1)); // move Person => calls MOVE-CONSTR

    Now let’s replace the two string constructors with one generic constructor perfect forwarding the passed argument to the member name:

    现在,让我们将两个string参数的构造函数替换为一个泛型构造函数,它将传入的参数完美转发给name成员。

    #include <utility>
    #include <string>
    #include <iostream>
    class Person
    {
    private:
        std::string name;
    public:
        // generic constructor for passed initial name:
        template<typename STR>
        explicit Person(STR&& n) : name(std::forward<STR>(n)) {
            std::cout << "TMPL-CONSTR for '" << name << "'
    ";
        }
        // copy and move constructor:
        Person(Person const& p) : name(p.name) {
            std::cout << "COPY-CONSTR Person '" << name << "'
    ";
        }
        Person(Person&& p) noexcept : name(std::move(p.name)) {
            std::cout << "MOVE-CONSTR Person '" << name << "'
    ";
        }
    };

    Construction with passed string works fine, as expected:

    与预期一样,传入string类型的对象时正常工作:

    std::string s = "sname";
    Person p1(s); //通过string对象转发 => 调用完美转发构造函数
    Person p2("tmp"); //通过字符串字面量初始化=> 调用完美转发构造函数

    Note how the construction of p2 does not create a temporary string in this case: The parameter STR is deduced to be of type char const[4]. Applying std::forward<STR> to the pointer parameter of the constructor has not much of an effect, and the name member is thus constructed from a null-terminated string.

    注意,现在构造p2时并不会创建一个临时的std::string对象。模板参数STR的类型被推导为char const[4]。但是将std::forward<STR>用于构造函数的指针参数并没有太大的意义,因此name成员将由一个以null结尾的字符串构造的。

    But when we attempt to call the copy constructor, we get an error:

    但是,当试图调用拷贝构造函数的时候,会出现错误:

    Person p3(p1); // ERROR

    while initializing a new Person by a movable object still works fine:

    而通过一个可移动对象来初始化Person对象则可以正常工作:

    Person p4(std::move(p1)); // OK: move Person => calls MOVECONST

    Note that also copying a constant Person works fine:

    注意,拷贝一个Person的const对象也是没问题的:

    Person const p2c("ctmp"); //通过字符串字面量来初始化const对象
    Person p3c(p2c); // OK: 拷贝const对象 =>调用Person(const Person&)

    The problem is that, according to the overload resolution rules of C++ (see Section 16.2.4 on page 333), for a nonconstant lvalue Person p the member template

    问题出在:根据C++重载解析规则(见第333页的16.2.4节),对于非const的左值Person p,成员模板

    template<typename STR>
    Person(STR&& n)

    is a better match than the (usually predefined) copy constructor:

    通常比预定义的拷贝构造函数更加匹配:

    Person (Person const& p)

    STR is just substituted with Person&, while for the copy constructor a conversion to const is necessary.

    这里的STR直接被替换成Person&,但是对于拷贝构造函数还需要做一个const转换

    You might think about solving this by also providing a nonconstant copy constructor:

    为了解决这一问题,你可能会考虑额外提供一个非const版本的拷贝构造函数:

    Person (Person& p);

    However, that is only a partial solution because for objects of a derived class, the member template is still a better match. What you really want is to disable the member template for the case that the passed argument is a Person or an expression that can be converted to a Person. This can be done by using std::enable_if<>, which is introduced in the next section.

    不过,这只是一个部分解决问题的方法。因为对于Person的派生类来讲,成员模板依然会更产生更精确的匹配。你真正想做的是:当传入一个Person对象或者一个可以转换为Person对象表达式时,禁用该成员模板。这可以通过使用std::enable_if<>来实现,它将在下一节中讲到。

    【编程实验】重载构造函数与完美转发构造函数时的问题

    #include <utility>
    #include <string>
    #include <iostream>
    #include <type_traits>
    class Person
    {
    private:
        std::string name;
    public:
        // generic constructor for passed initial name:
        template<typename STR>
        Person(STR&& n) : name(std::forward<STR>(n)) {
            std::cout << "TMPL-CONSTR for '" << name << "'
    ";
        }
        // copy and move constructor:
        Person(Person const& p) : name(p.name) {
            std::cout << "COPY-CONSTR Person '" << name << "'
    ";
        }
        Person(Person&& p) noexcept : name(std::move(p.name)) {
            std::cout << "MOVE-CONSTR Person '" << name << "'
    ";
        }
    };
    
    class SpecialPerson : public Person
    {
    public:
        using Person::Person; //继承构造函数
    
        //以下两个函数会从父类继承过来,因此无需手动实现。但为了看清楚其声明,特重新
        //罗列出来:
    
        //拷贝构造函数
        //由于sp的类型为SpecialPerson。当调用Person(sp)时,完美构造函数会产生更精确的匹配
        //Person(const SpecialPerson&),在这个构造函数中string类型的name成员用子类SpecialPerson
        //对象初始化,显然会编译失败。注意这里Person的构造函数
        //SpecialPerson(const SpecialPerson& sp) : Person(sp) {}  
    
        //移动构造函数
        //SpecialPerson(SpecialPerson&& sp) noexcept: Person(std::move(sp)) {}
    };
    
    
    int main() {
        std::string s = "sname";
        Person p1(s); //用string对象初始化 => 调用Person(const string&)
    
        //1. 完美构造函数产生更精确的匹配:
        //Person p2(p1); //error,完美转发构造函数会产生更加精确的匹配Person(Person&),但是在
                         //该函数中,当初始化name时会执行name(std::forward<Person>(p1)),由于
                         //name是std::string类型,并不没有提供这样一个通过Person对象来初始化
                         //的构造函数,因此编译失败。
    
        //2. Person子类的拷贝构造和移动构造:
        //由于子类构造时,通过Person(sp)/Person(std::move(sp)调用了父类构造函数,此时父类的造
        //完美转发函数中将产生一个匹配的构造函数。而在这个函数中会用子类SpecialPerson对象来
        //初始化std::string类型的name对象,这显然也是不能通过的。
        //SpecialPerson sp("spname");
        //SpecialPerson sp1(sp);
        //SpecialPerson sp2(std::move(sp));
    
        //3.解决方案:就是有条件禁用父类的完美转发构造函数。即当通过Person及其子类对象创建
        //对象时,不调用完美转发构造函数,而是转为调用普通的拷贝构造或移动构造函数。
        //如将Person的完美构造函数声明为:
        //template<typename STR, typename = std::enable_if_t < !std::is_base_of_v<Person, std::decay_t<STR>>>>
        //Person(STR && n) : name(std::forward<STR>(n)) {
        //    std::cout << "TMPL-CONSTR for '" << name << "'
    ";
        //}
       
        return 0;
    }
  • 相关阅读:
    Spring MVC 框架搭建及详解
    设计模式应用案例(上)
    UAC权限
    General Structure of Quartz.NET and How To Implement It
    求比指定数大且最小的“不重复数”问题
    Getting Started with Core Data
    HDU 2034 人见人爱A-B
    第九届蓝桥杯JavaC组决(国)赛真题
    第九届蓝桥杯JavaC组决(国)赛真题
    第九届蓝桥杯JavaC组决(国)赛真题
  • 原文地址:https://www.cnblogs.com/5iedu/p/12774380.html
Copyright © 2011-2022 走看看