大家来讨论一下 exercise 12.3

Queequeg

来自: Queequeg
2014-09-01 20:22:25

×
加入小组后即可参加投票
  • pezy

    pezy (Brevity Can Be a Virtue) 2014-09-02 01:25:17

    看了好几遍,总算看明白你说的问题了。。。 很有道理!@戳戳的结论是对的,不应该加const. 一个const的对象,通过它的某public成员方法,竟然可以修改其数据成员的值。那也太不符合常理了。 如果我们对 push_back 和 pop_back 不加const , 那么上述逻辑将会被编译器检测出来,从而提醒我们。如果加了,后果不堪设想呀! 还是用代码说事:我基于@戳戳 的代码改的(@戳戳 这一章的代码已有神功初成的感觉了;) ): https://gist.github.com/pezy/f25780e0fe026e05e833#file-ex12_1_2_3_4_5-cpp-L95 对于kStrBlob(const)来说,竟然可以push_back成功,还可以pop_back成功。过于反人类了。 所以,这const加不得! 。。。。扯了这么多,你是这个意思么?

  • pezy

    pezy (Brevity Can Be a Virtue) 2014-09-02 01:32:42

    我再补充一点资料: 12.3对应的知识点: https://isocpp.org/wiki/faq/const-correctness#const-member-fns 12.2对应的知识点: https://isocpp.org/wiki/faq/const-correctness#return-const-ref-from-const-memfn 对于12.2,@戳戳 直接给出了答案,可能觉得这个点已经比较清楚了。

  • 第四只猴子

    第四只猴子 组长 2014-09-02 05:23:49

    sorry@Queequeg.. 我看了几遍主贴,但没太懂。。下面是我看懂的部分。。 【但是感觉给出的答案似乎有问题。】 --什么问题呢? 【编译的时候系统报错 error C2662:。。】 --我能说的是,这个报错是预期之中的,细节参见@pezy的回复。 你所说的问题,是指这个报错吗? 剩下的部分,没太看懂。。

  • 第四只猴子

    第四只猴子 组长 2014-09-02 06:10:00

    先说一下,我不加const 版本的push_back的原因: OO的三大理念:抽象,继承和多态。 此处只涉及抽象,或称封装。用户无需关心class的实现细节,只要调用成员函数即可。假使我们给出了const版本的push_back,那么当用户定义一个【const wy_StrBlob对象my_StrBlob】时,并调用push_back: my_StrBlob.push_back(something); --这行代码会被编译通过,并把something装进my_StrBlob。 ----从class的语法细节看这是没问题的,因为私有成员‘data’并没有被改变,改变的是data指向的容器。 ----从用户程序员的角度就奇葩了,“我明明定义的是const对象,怎么还能执行push_back?”,即@pezy所称的【反人类】。 ----另外,std::string,std::vector,std::deque都只定义了非const版本。const的string或者vector执行push_back的时候一定会报错。跟std保持一致性也是不加const的一证。 综上,如果const版push_back加上去的话,坏处有: 1、迫使用户程序员去了解class的实现细节,违反OO原则 2、违反std的使用习惯。 故,不加。

  • 第四只猴子

    第四只猴子 组长 2014-09-02 06:59:57

    另,补个心得。 CP这本书设计的相当有匠心,仔细阅读可以发现多条“叙事”线索,或明或暗,或主或次。如【动态数组】,【callable】、【指针】等等。这在叙事类作品里也是优秀设计,在教材类里是不多见的。 其中,【动态数组】这条线相当的长: 首次出场是第三章,vector,string和array【明出】。 最后一次出场是第十六章,习题16.16实现vector的模板【明出】。 3-16章之间,【动态数组】还有多次出场。很重要的一个【明出】在第十三章,13.5,StrVec的实现。按书中的话,StrVec和std::vector的原理是一致的,唯一区别就是没用模板。实现的核心是用3个pointer来管理动态内存,细节见13.5。 回到本帖讨论的话题,十二章这个StrBlob其实是【动态数组】的【暗出】,是为了13.5,StrVec的明出作知识储备。而12.3这道题,完全可以参考std::vector的设计,因为StrBlob是【动态数组】这条线逐步迈向std::vector的一环。std::vector往往也是用3个指针管理内存,而StrBlob简化成了单指针,又通过std::vector和智能指针把内存管理的allocation和deallocation抽象掉了。 注:【动态数组】这个词是采用的数据结构的命名,我用这个词来描述这条看不见但事实存在的线索。CP里没怎么太提这个词儿。

  • Queequeg

    Queequeg 楼主 2014-09-02 08:35:48

    sorry@Queequeg.. 我看了几遍主贴,但没太懂。。下面是我看懂的部分。。 【但是感觉给出的答 sorry@Queequeg.. 我看了几遍主贴,但没太懂。。下面是我看懂的部分。。 【但是感觉给出的答案似乎有问题。】 --什么问题呢? 【编译的时候系统报错 error C2662:。。】 --我能说的是,这个报错是预期之中的,细节参见@pezy的回复。 你所说的问题,是指这个报错吗? 剩下的部分,没太看懂。。 ... 第四只猴子

    这个回复解释很清楚,我只从语法细节角度来考虑问题了,@戳戳的解释让我一下子明白了怎么回事儿,非常感谢!

  • Queequeg

    Queequeg 楼主 2014-09-02 08:37:35

    看来我下次阐述问题的时候应该分段叙述,昨天比较忙,偷懒了

  • 第四只猴子

    第四只猴子 组长 2014-09-02 09:31:49

    这个回复解释很清楚,我只从语法细节角度来考虑问题了,@戳戳的解释让我一下子明白了怎么回事儿 这个回复解释很清楚,我只从语法细节角度来考虑问题了,@戳戳的解释让我一下子明白了怎么回事儿,非常感谢! ... Queequeg

    不用客气,能讨论、总结一下,我自己也很有收获。 相信这是每个人都会遇到的疑问。我去年12月末做的这道题,当时并没有明确的答案,就直接copy了SO上的回复。现在讨论一下,再查查资料就清晰多了。 期待你的新发现。

  • 第四只猴子

    第四只猴子 组长 2014-09-02 10:06:36

    看了好几遍,总算看明白你说的问题了。。。 很有道理!@戳戳的结论是对的,不应该加const. 看了好几遍,总算看明白你说的问题了。。。 很有道理!@戳戳的结论是对的,不应该加const. 一个const的对象,通过它的某public成员方法,竟然可以修改其数据成员的值。那也太不符合常理了。 如果我们对 push_back 和 pop_back 不加const , 那么上述逻辑将会被编译器检测出来,从而提醒我们。如果加了,后果不堪设想呀! 还是用代码说事:我基于@戳戳 的代码改的(@戳戳 这一章的代码已有神功初成的感觉了;) ): https://gist.github.com/pezy/f25780e0fe026e05e833#file-ex12_1_2_3_4_5-cpp-L95 对于kStrBlob(const)来说,竟然可以push_back成功,还可以pop_back成功。过于反人类了。 所以,这const加不得! 。。。。扯了这么多,你是这个意思么? ... pezy

    嗯,前几章写得太乱来了。。后面的要规矩一些。。

  • Queequeg

    Queequeg 楼主 2014-09-02 11:36:11

    我总结一下上面的内容,以方便后来者更好的理解这个问题。 一、看了@戳戳的解释后对exercise12.3的重新理解。看待这个问题,最重要的是要站在类的使用者的角度来看,而不是类的设计者的角度。虽然在类的具体实现中,数据成员是一个指向vector<string>的智能指针;但由于类的封装,在类的使用者看来,数据成员是vector<string>,他们并不知道具体的实现使用了智能指针。那么当类的使用者声明类的常量对象时,他们期待的结果是vector<string>的内容不会被改变。所以我们在设计这个类的时候,要考虑到类的使用者的真实意图,对于像push_back和pop_back这样会改变智能指针所指向的vector<string>内容的成员函数,我们不应该声明和定义成const版本。这样在类的使用者使用类的常对象时,就不能调用push_back和pop_back成员函数,不能改变智能指针所指向的vector<string>的内容了,这正好与类的使用者的意图相符。 二、对于我提出的初始问题的后半部分的解释。 后半部分说的其实是编译器的行为,当一个类的常量对象试图访问这个类的非const成员函数时,会遭到拒绝。原因是编译器认为一个类的非const成员函数可能有改变数据成员的风险,对于这个类的常量对象,这种风险是不能接受的。所有编译器只允许类的常量对象访问类的const成员函数。编译器这种行为的深层原因可以从报错信息看出来,当用一个类的常量对象访问这个类的非const成员函数时,常量对象会把它的this指针(在此是一个指向常量的指针)作为一个隐式参数传递给成员函数。而非const的成员函数接受的是普通的this指针,常量对象传递给它一个指向常量的this指针,这个指向常量的this指针是无法转换成普通this指针的,所有这种情况下会报出类似 “不能将“this”指针从“const wy_StrBlob”转换为“wy_StrBlob &”” 的错误。 又写了这么长,不知道有没有人会耐心看完呢。。。。。囧

  • pezy

    pezy (Brevity Can Be a Virtue) 2014-09-02 12:02:34

    我总结一下上面的内容,以方便后来者更好的理解这个问题。 一、看了@戳戳的解释后对exercise12. 我总结一下上面的内容,以方便后来者更好的理解这个问题。 一、看了@戳戳的解释后对exercise12.3的重新理解。看待这个问题,最重要的是要站在类的使用者的角度来看,而不是类的设计者的角度。虽然在类的具体实现中,数据成员是一个指向vector&lt;string&gt;的智能指针;但由于类的封装,在类的使用者看来,数据成员是vector&lt;string&gt;,他们并不知道具体的实现使用了智能指针。那么当类的使用者声明类的常量对象时,他们期待的结果是vector&lt;string&gt;的内容不会被改变。所以我们在设计这个类的时候,要考虑到类的使用者的真实意图,对于像push_back和pop_back这样会改变智能指针所指向的vector&lt;string&gt;内容的成员函数,我们不应该声明和定义成const版本。这样在类的使用者使用类的常对象时,就不能调用push_back和pop_back成员函数,不能改变智能指针所指向的vector&lt;string&gt;的内容了,这正好与类的使用者的意图相符。 二、对于我提出的初始问题的后半部分的解释。 后半部分说的其实是编译器的行为,当一个类的常量对象试图访问这个类的非const成员函数时,会遭到拒绝。原因是编译器认为一个类的非const成员函数可能有改变数据成员的风险,对于这个类的常量对象,这种风险是不能接受的。所有编译器只允许类的常量对象访问类的const成员函数。编译器这种行为的深层原因可以从报错信息看出来,当用一个类的常量对象访问这个类的非const成员函数时,常量对象会把它的this指针(在此是一个指向常量的指针)作为一个隐式参数传递给成员函数。而非const的成员函数接受的是普通的this指针,常量对象传递给它一个指向常量的this指针,这个指向常量的this指针是无法转换成普通this指针的,所有这种情况下会报出类似 “不能将“this”指针从“const wy_StrBlob”转换为“wy_StrBlob &amp;”” 的错误。 又写了这么长,不知道有没有人会耐心看完呢。。。。。囧 ... Queequeg

    看完了,写的明白多了。给个赞。

  • 第四只猴子

    第四只猴子 组长 2014-09-02 12:49:41

    我总结一下上面的内容,以方便后来者更好的理解这个问题。 一、看了@戳戳的解释后对exercise12. 我总结一下上面的内容,以方便后来者更好的理解这个问题。 一、看了@戳戳的解释后对exercise12.3的重新理解。看待这个问题,最重要的是要站在类的使用者的角度来看,而不是类的设计者的角度。虽然在类的具体实现中,数据成员是一个指向vector&lt;string&gt;的智能指针;但由于类的封装,在类的使用者看来,数据成员是vector&lt;string&gt;,他们并不知道具体的实现使用了智能指针。那么当类的使用者声明类的常量对象时,他们期待的结果是vector&lt;string&gt;的内容不会被改变。所以我们在设计这个类的时候,要考虑到类的使用者的真实意图,对于像push_back和pop_back这样会改变智能指针所指向的vector&lt;string&gt;内容的成员函数,我们不应该声明和定义成const版本。这样在类的使用者使用类的常对象时,就不能调用push_back和pop_back成员函数,不能改变智能指针所指向的vector&lt;string&gt;的内容了,这正好与类的使用者的意图相符。 二、对于我提出的初始问题的后半部分的解释。 后半部分说的其实是编译器的行为,当一个类的常量对象试图访问这个类的非const成员函数时,会遭到拒绝。原因是编译器认为一个类的非const成员函数可能有改变数据成员的风险,对于这个类的常量对象,这种风险是不能接受的。所有编译器只允许类的常量对象访问类的const成员函数。编译器这种行为的深层原因可以从报错信息看出来,当用一个类的常量对象访问这个类的非const成员函数时,常量对象会把它的this指针(在此是一个指向常量的指针)作为一个隐式参数传递给成员函数。而非const的成员函数接受的是普通的this指针,常量对象传递给它一个指向常量的this指针,这个指向常量的this指针是无法转换成普通this指针的,所有这种情况下会报出类似 “不能将“this”指针从“const wy_StrBlob”转换为“wy_StrBlob &amp;”” 的错误。 又写了这么长,不知道有没有人会耐心看完呢。。。。。囧 ... Queequeg

    应该是明白你的意思的,你是通过编译器的报错信息反推C++语法规则。这是一个不错的学习方法。 值得注意的是语法行为分为至少三个层次:well defined、implementation defined和undefined。ISO C++ 标准只规定第一个层次,其他都取决于编译器厂家。所以反推存在一个风险:错把编译器的实现当成C++标准,这个是值得小心的。 当然,留心编译器反馈是非常赞的。

  • 御宅暴君

    御宅暴君 2015-07-13 20:46:49

    @戳戳 原来你的答案是不加,提到的两处原因也有理有据。不过是不是该完善 https://github.com/Mooophy/Cpp-Primer/blob/master/ch12/README.md#exercise-123 了? 没耐心的读者可能会误认为 David Schwartz 的答案是对的,并没有深究 Discussion over this exercise more on douban(chinese), 就被误导了。

  • 第四只猴子

    第四只猴子 组长 2015-07-14 15:36:51

    @戳戳 原来你的答案是不加,提到的两处原因也有理有据。不过是不是该完善 https://github.com/Mo @戳戳 原来你的答案是不加,提到的两处原因也有理有据。不过是不是该完善 https://github.com/Mooophy/Cpp-Primer/blob/master/ch12/README.md#exercise-123 了? 没耐心的读者可能会误认为 David Schwartz 的答案是对的,并没有深究 Discussion over this exercise more on douban(chinese), 就被误导了。 ... 御宅暴君

    刚开始学C++的时候,David Schwartz曾多次在SO上回答我的提问,给了我很大帮助和鼓励。所以,不太想修正他的原话。 另外,他说的没问题啊,最多立场有点儿暧昧。

  • Chase07

    Chase07 2017-03-29 17:47:56

    先说一下,我不加const 版本的push_back的原因: OO的三大理念:抽象,继承和多态。 此处只 先说一下,我不加const 版本的push_back的原因: OO的三大理念:抽象,继承和多态。 此处只涉及抽象,或称封装。用户无需关心class的实现细节,只要调用成员函数即可。假使我们给出了const版本的push_back,那么当用户定义一个【const wy_StrBlob对象my_StrBlob】时,并调用push_back: my_StrBlob.push_back(something); --这行代码会被编译通过,并把something装进my_StrBlob。 ----从class的语法细节看这是没问题的,因为私有成员‘data’并没有被改变,改变的是data指向的容器。 ----从用户程序员的角度就奇葩了,“我明明定义的是const对象,怎么还能执行push_back?”,即@pezy所称的【反人类】。 ----另外,std::string,std::vector,std::deque都只定义了非const版本。const的string或者vector执行push_back的时候一定会报错。跟std保持一致性也是不加const的一证。 综上,如果const版push_back加上去的话,坏处有: 1、迫使用户程序员去了解class的实现细节,违反OO原则 2、违反std的使用习惯。 故,不加。 ... 第四只猴子

    涉及OO理念的解释,使得本小白的小脑为之一振~

  • SoFarAway

    SoFarAway 2017-05-04 17:59:33

    我总结一下上面的内容,以方便后来者更好的理解这个问题。 一、看了@戳戳的解释后对exercise12. 我总结一下上面的内容,以方便后来者更好的理解这个问题。 一、看了@戳戳的解释后对exercise12.3的重新理解。看待这个问题,最重要的是要站在类的使用者的角度来看,而不是类的设计者的角度。虽然在类的具体实现中,数据成员是一个指向vector&lt;string&gt;的智能指针;但由于类的封装,在类的使用者看来,数据成员是vector&lt;string&gt;,他们并不知道具体的实现使用了智能指针。那么当类的使用者声明类的常量对象时,他们期待的结果是vector&lt;string&gt;的内容不会被改变。所以我们在设计这个类的时候,要考虑到类的使用者的真实意图,对于像push_back和pop_back这样会改变智能指针所指向的vector&lt;string&gt;内容的成员函数,我们不应该声明和定义成const版本。这样在类的使用者使用类的常对象时,就不能调用push_back和pop_back成员函数,不能改变智能指针所指向的vector&lt;string&gt;的内容了,这正好与类的使用者的意图相符。 二、对于我提出的初始问题的后半部分的解释。 后半部分说的其实是编译器的行为,当一个类的常量对象试图访问这个类的非const成员函数时,会遭到拒绝。原因是编译器认为一个类的非const成员函数可能有改变数据成员的风险,对于这个类的常量对象,这种风险是不能接受的。所有编译器只允许类的常量对象访问类的const成员函数。编译器这种行为的深层原因可以从报错信息看出来,当用一个类的常量对象访问这个类的非const成员函数时,常量对象会把它的this指针(在此是一个指向常量的指针)作为一个隐式参数传递给成员函数。而非const的成员函数接受的是普通的this指针,常量对象传递给它一个指向常量的this指针,这个指向常量的this指针是无法转换成普通this指针的,所有这种情况下会报出类似 “不能将“this”指针从“const wy_StrBlob”转换为“wy_StrBlob &amp;”” 的错误。 又写了这么长,不知道有没有人会耐心看完呢。。。。。囧 ... Queequeg

    宝宝很耐心的看完了。。说得真好!很受启发! const Semantic,const member function的相关问题真是得好好琢磨才行! 另外,你可能误会David Schwartz的回答的意思了,我认为他的回答里的意思是:如果你重载了const的push_back、pop_back(),然后在正常使用情况下(也就是你对const对象使用push_back),此时会调用const版本的push_back(),编译器不会报错,尽管你看似改变了数据但是编译器不会报错,因为类的数据成员是指向vector<string>的智能指针,push_back()并没有改变这个指针的值。 我试验了一下,加了const版的push_back(),然后类的常对象也可以使用push_back()了。 小白一枚,不知道说得对没对= =

你的回应

回应请先 , 或 注册

↑回顶部