标题:最近学习C++,书上红字标出的话不太理解,求大神解释!
只看楼主
uouo99
Rank: 2
等 级:论坛游民
威 望:3
帖 子:30
专家分:98
注 册:2019-9-30
结帖率:50%
已结贴  问题点数:20 回复次数:8 
最近学习C++,书上红字标出的话不太理解,求大神解释!

effective modern C++ 书的部分原文如下:

在另一方面,一个universal引用可能(译注:只是可能不是一定)被绑定到一个有资格被move的对象上去。universal引用只在它由右值初始化的时候需要被转换成一个右值。Item 23解释了这就是std::forward具体做的事情:

程序代码:
    class Widget {
    public:
        template<typename T>
        void setName(T&& newName)               // newName是一个
        { name = std::forward<T>(newName); }    // universal引用

        ...
    };


总之,因为右值引用总是被绑定到右值,所以当它们被转发给别的函数的时候,应该被无条件地转换成右值(通过std::move),而universal引用由于只是不定时地被绑定到右值,所以当转发它们时,它们应该被有条件地转换成右值(通过std::forward)。

Item 23解释了对右值引用使用std::forward能让它显示出正确的行为,但是源代码会因此变得冗长、易错、不符合习惯的,所以你应该避免对右值引用使用std::forward。对universal引用使用std::move是更加糟糕的想法,因为这样会对左值(比如,局部变量)产生非预期的修改:

程序代码:
    class Widget {
    public:
        template<typename T>
        void setName(T&& newName)       // universal引用
        { name = std::move(newName); }  // 能通过编译,但是
        ...                             // 这代码太糟糕了

    private:
        std::string name;
        std::shared_ptr<SomeDataStructure> p;
    };

    std::string getWidgetName();        // 工厂函数

    Widget w;                           

    auto n = getWidgetName();           // n是局部变量

    w.setName(n);                       // 把n move到w中去!

    ...                                 // n的值现在是未知的


这里,局部变量n被传给w.setName,调用者完全可以假设这是一个对n只读的操作。但是因为stdName在内部会用了std::move,然后无条件地将他的引用参数转换成了右值,所以n的值将被move到w.name中去,最后在setNamen调用完成之后,n将成为一个未知的值。这样的行为会让调用者很沮丧,甚至会气得砸键盘!

你可能指出stdName不应该声明它的参数为universal引用。虽然这样的引用不能是const的(看Item 24,译注:加const就成右值引用了),但是steName确实不应该修改它的参数。你还可能指出如果setName使用const 左值和右值进行重载,整个问题将被避免。像是这样:

程序代码:
    class Widget {
    public:
        void setName(const std::string& newName)    // 从const左值来set
        { name = newName; }

        void setName(std::string&& newName)         // 从右值来set
        { name = std::move(newName); }

        ...
    };


在这种情况下,确实能工作,但是这种方法是有缺点的。首先,它增加了源代码里要编写以及维护的代码量(使用两个函数代替一个简单的模板)。其次,它更加低效。举个例子,考虑这个setName的使用:

    w.setName("Adela Novak");


使用universal引用版本的setName,在“Adela Novak”字符串被传给setName时,它会被转发给处于w对象中的一个std::string(就是w.name)的operator=(译注:const char版本的operator=)函数。因此,w的name数据成员将是用字符串直接赋值的;没有出现一个临时的std::string对象。然而,使用重载版本的setName,为了让setName的参数能绑定上去,一个临时的std::string对象将被创建,然后这个临时的std::string对象将被移动到w的数据成员中去。因此这个setName的调用需要执行一次std::string的构造函数(为了创建临时对象),一个std::string的move operator=(为了move newName到w.name中去),以及一个std::string的析构函数(为了销毁临时对象)。对于const char 指针来说,比起只调用std::string的operator=,上面这些函数就是多花的代价。额外的代价有可能随着实现的不同而产生变化,并且代价是否值得考虑也将随着应用和函数库的不同而产生变化。不管怎么说,事实就是,在一些情况下,使用一对重载了左值和右值的函数来替换带universal引用参数的函数模板有可能增加运行期的代价。如果我们推广这个例子,使得Widget的数据成员可以是任意类型的(不仅仅是熟知的std::string),性能的落差将更大,因为不是所有类型的move操作都和std::string一样便宜的。

我的疑问:
在使用重载版本的setName函数时,w.setName("Adela Novak"); 这句代码难道不是调用的void setName(const std::string& newName)这个函数吗?根据字符串字面值“Adela Novak”创建的临时string对象绑定到函数参数newName,然后执行name = newName;这句代码,其中的newName因为是函数参数,肯定是一个左值,把左值赋值给w的成员name,难道不是应该调用复制赋值运算符吗?为什么书上说是调用移动到w的数据成员中去(move operator=) ?求大神解惑。


[此贴子已经被作者于2019-10-5 10:30编辑过]

搜索更多相关主题的帖子: string name move 引用 std 
2019-10-05 10:26
雪影辰风
Rank: 6Rank: 6
来 自:衡阳市
等 级:贵宾
威 望:22
帖 子:177
专家分:387
注 册:2019-6-17
得分:0 
如果不介意能不能教我public是怎么用的?
2019-10-05 14:08
rjsp
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:版主
威 望:507
帖 子:8890
专家分:53117
注 册:2011-1-18
得分:20 
在使用重载版本的setName函数时,w.setName("Adela Novak"); 这句代码难道不是调用的void setName(const std::string& newName)这个函数吗?

它生成了一个临时对象,应该调用右值引用的那个重载
2019-10-06 12:10
uouo99
Rank: 2
等 级:论坛游民
威 望:3
帖 子:30
专家分:98
注 册:2019-9-30
得分:0 
回复 3楼 rjsp
"Adela Novak"是一个字符串字面值,类型是const char (*)[12],由于具有const属性,不应该是调用const版本的那个重载的函数吗?


[此贴子已经被作者于2019-10-6 13:52编辑过]

2019-10-06 12:21
uouo99
Rank: 2
等 级:论坛游民
威 望:3
帖 子:30
专家分:98
注 册:2019-9-30
得分:0 
以下是引用rjsp在2019-10-6 12:10:38的发言:

 
它生成了一个临时对象,应该调用右值引用的那个重载
我刚才用VS2017测试了一下,确实是优先调用那个右值引用的函数,但是我不能理解的地方是:1.本身字符串字面值是一个const char的数组,是常量。2.对字符串字面值是可以取地址的,也就是说它本身是一个左值,比如cout << &"Adela Novak" << endl;这样是可以正确得到地址的。按道理来说函数中的右值引用参数只能接收一个右值才对。
2019-10-06 13:23
rjsp
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:版主
威 望:507
帖 子:8890
专家分:53117
注 册:2011-1-18
得分:0 
听不懂你的意思,这和"Adela Novak"是什么类型有啥关系?
只与 std::string("Adela Novak") 是什么类型相关。
看代码:
void setName(const std::string& newName)
void setName(std::string&& newName)
2019-10-06 14:10
uouo99
Rank: 2
等 级:论坛游民
威 望:3
帖 子:30
专家分:98
注 册:2019-9-30
得分:0 
以下是引用rjsp在2019-10-6 14:10:56的发言:

听不懂你的意思,这和"Adela Novak"是什么类型有啥关系?
只与 std::string("Adela Novak") 是什么类型相关。
看代码:
void setName(const std::string& newName)
void setName(std::string&& newName)

原谅我是新手,问题比较多哈,但是从"Adela Novak"  到  std::string("Adela Novak")的类型变换不是应该在决定选择哪个重载的setName函数后才进行的吗?不然程序怎么知道需要转换到string类型呢?因为一开始其实就是个字符串字面值常量,这个决定使用哪个重载函数的过程是怎么样的呢?
2019-10-06 15:56
rjsp
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:版主
威 望:507
帖 子:8890
专家分:53117
注 册:2011-1-18
得分:0 
不好意思,还是听不懂。
不管是选择哪个重载,都要先产生临时对象 std::string("Adela Novak")。
那么对于 std::string("Adela Novak") 这个临时对象而言,
void setName(const std::string& newName)
void setName(std::string&& newName)
中你觉得哪个更适合?
2019-10-06 20:02
uouo99
Rank: 2
等 级:论坛游民
威 望:3
帖 子:30
专家分:98
注 册:2019-9-30
得分:0 
以下是引用rjsp在2019-10-6 20:02:17的发言:

不好意思,还是听不懂。
不管是选择哪个重载,都要先产生临时对象 std::string("Adela Novak")。
那么对于 std::string("Adela Novak") 这个临时对象而言,
void setName(const std::string& newName)  
void setName(std::string&& newName)
中你觉得哪个更适合?

谢谢,如果直接是string的临时变量作为实参去调用,当然void setName(std::string&& newName) 更匹配,但是现在是个字符串字面值,编译器怎么就知道选择哪个的呢?难道编译器先挨个去产生临时对象试一遍- -~
好吧,可能是我想多了,我只是比较好奇这个函数重载的匹配规则具体是怎么样的。。再次感谢回答
2019-10-06 20:23



参与讨论请移步原网站贴子:https://bbs.bccn.net/thread-497107-1-1.html




关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.025778 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved