09 | 易用性改进 II:字面量、静态断言和成员函数说明符
09 | 易用性改进 II:字面量、静态断言和成员函数说明符
讲述:吴咏炜
时长13:34大小9.30M
自定义字面量
二进制字面量
数字分隔符
静态断言
default 和 delete 成员函数
override 和 final 说明符
内容小结
课后思考
参考资料
赞 4
提建议
精选留言(23)
- 木瓜7772019-12-17您好,您有没有感觉比较好的开源c++项目推荐? 希望从别人的项目中学到一些经验,谢谢!
作者回复: C++ 项目入门都不容易,要找自己有兴趣的领域是关键。除了第 6 讲评论里推荐的那些,可以考虑下面两个(我将来也会讲到): - EasyLogging++ - Catch2
共 2 条评论11 - zhengfan2020-07-01吴老师,您好。 请问您在static_assert中使用的例子,是不是应该判断alignment && (alignment & (alignment - 1) ==0)? 另外,这个算法真是精巧👍。效率之高让我甚至一度怀疑命题的等价性。证明了一下确实是等价的。
作者回复: 0 也是 2 的整数次幂,没毛病。😁 这是很标准的算法,我很早就看到过。 从实用的角度,这一个判断可能确实不够。我的实际代码里有: STATIC_ASSERT(alignment::value > 0 && alignment::value <= 8192, Bad_alignment); STATIC_ASSERT((alignment::value & (alignment::value - 1)) == 0, Alignment_must_be_power_of_two);
共 3 条评论3 - 三味2019-12-20如果对一个函数声明了final, 我觉得没有必要在添加override了吧. override就是明确声明这是一个继承来的函数, final同样也是这个意思, 只不过final更霸道, 后续的不要在继承了! 如果从一开始就不想让别的函数去继承而写final, 那就根本没必要去virtual它. 何必要在虚函数表中添加一个没有继承作用的虚函数呢? PS: 数字分隔符和自定义字面量真是学到了. 在9102年的尾巴, 我才知道有这么邪道的用法... PPS: 前些日子看了一些国外游戏大牛各种喷C++的帖子. 本章的所有内容应该都是他们喷的范围吧... 那些人特别看中编译时间, 追求极致的运行效率... 有个人专门对比了int a=7; 和 int a{7}的性能差别...从编译到运行时间... 利用宏展开的方式, 对这两个例子分别做了百万次展开, 如果用vs测试都能爆IDE内存的级别... 我觉得对于中小型对性能不是特别敏感的程序, 这些还是很有用的. PPPS: 最近对Data-oriented design感兴趣, 不知道作者以后是否有开这类理论实战课程的计划捏? 我搜索上面喷神, 就是从这里开始搜索出来的...展开
作者回复: 对,final override 合法但不必要。 当然不是所有内容都是被那个游戏开发人士喷的。他抱怨的是会导致编译速度下降,以及非优化编译性能差的那部份功能。通常都是模板相关的。所以这一讲的内容不属于其中。
4 - Geek37262021-10-29硬着头皮看了基础篇,才发现,自己是没有基础的; 去补补C++的知识再来,虽然在公司开发C++的项目,但大部份的代码都是C语言写的,落后了。2
- 小奶狗2020-09-15代码如下: #include <iostream> #include <memory> using namespace std; class dummy{ }; class Boy{ public: Boy(){ cout<<"boy constructor"<<endl; } Boy(Boy& m){ cout<<"boy copy constructor"<<endl; } Boy(Boy&& m){ cout<<"boy move constructor"<<endl; } }; class Man{ public: Man(){ cout<<"man constructor "<<endl; } Man(Boy boy){ cout<<"man constructor with boy "<<endl; } Man(Man &m){ cout<<"copy constructor"<<endl; } Man(Man&& m){ cout<<"move constructor"<<endl; } private: Boy a; }; int main(){ dummy d; dummy d2; d = d2; dummy d3 = d2; std::cout << "***************" <<std::endl; Boy&& boy = Boy(); Boy& b = boy; std::cout << "/////////////" <<std::endl; make_shared<Man>(boy); std::cout << "###############" <<std::endl; make_shared<Man>(move(boy)); } 输出的结果: *************** boy constructor ///////////// boy copy constructor boy constructor 疑惑:为什么会调用Boy的构造函数呢? man constructor with boy ############### boy move constructor boy constructor man constructor with boy Process finished with exit code 0 老师,如上“疑惑”所述,为什么会调用Boy的构造函数呢?我觉得不应该调用Boy的构造函数,因为调用了boy 拷贝构造,然后再调用Man的拷贝构造,就可以了。想不通。展开
作者回复: 你在 Man 类里有私有成员变量 Boy a,又没有去初始化,那不就默认构造了么。 如果你想用参数 boy 去拷贝构造 a,你需要写: Man(Boy boy) : a(boy) { cout << "man constructor with boy " << endl; } 这样会拷贝构造两次。我想你真正需要的可能是(我加上了拷贝构造函数的 const): class Boy { public: Boy() { cout << "boy constructor" << endl; } Boy(const Boy& m) { cout << "boy copy constructor" << endl; } Boy(Boy&& m) { cout << "boy move constructor" << endl; } }; class Man { public: Man() { cout << "man constructor " << endl; } Man(const Boy& boy) : a(boy) { cout << "man constructor with boy " << endl; } Man(Man& m) { cout << "copy constructor" << endl; } Man(Man&& m) { cout << "move constructor" << endl; } private: Boy a; };
2 - 王旧业2020-03-15length的例子里,字面量operator""实现中参数是long double,但是length成员变量value使用的是double,这是有意为之吗?
作者回复: 哦,这是因为 operator"" 的参数要求是 long double(不是 double)。而一般的类里,double 的精度就够用了。倒不算什么刻意为之吧。
2 - geek2021-03-05老师,关于子类中重新定义虚函数这个动作我之前认为是重写(overwrite),但也碰到有人说是重载(overload),另外我看文章中说的是覆写(就是override的翻译吧?)。 我个人倾向于overwrite或者override,因为重新定义虚函数这个动作涉及了多个类,而重载是指同一个类中的一组同名但形参个数或者形参类型不同的方法。我认为override是包含overwrite的,overwrite只说子类重新实现基类虚函数这种情况,而override则是覆盖,只要子类有了和基类中同名的方法,都会隐藏父类中的同名方法,如果子类能创建对象,必须要实现虚函数,因而就会隐藏基类中的同名方法。 老师看下我说的有问题吗?展开
作者回复: 对。override 的函数的参数是一样的,子类覆盖父类。overload 的函数的参数是不一样的,根据参数来选择合适的函数。两者不能混淆。
1 - Milittle2020-02-25之前用default delete override final较多 static_assert 和 自定义字面量和二进制字面量用的少 争取以后可以使用在自己的项目中 提供便利 老师的硬货很多 受益匪浅 感谢
作者回复: 小改进,适合的就用起来。但也不用强求为了用而用。
1 - 小一日一2019-12-16我最喜欢的C++易用性改进及理由: auto: 少打字 scope for:少打字 类成员默认补始化语法:少打字 default 和 delete 成员函数:简化对类行为的控制难度 自定义字面量:代码看起来舒服。展开
作者回复: 都用上了吗?很好啊。
共 2 条评论1 - 瓜农2022-06-04一直有种感觉,相对于java,c++的语法设计略显随意不够自洽。 譬如const/final在java里面就final搞定了,vitual这个关键字也感觉比较鸡肋。 老师怎么看
作者回复: 如果你不需要性能,那你可以有抽象和优雅。见 Python。 如果你不需要抽象,那你可以有简单和性能(但程序员用起来会很费力)。见 C。 如果你不需要向后兼容性,用几十年的经验设计一种全新的语言,那你也至少可以做到兼顾抽象、优雅和性能(简单是简单不了了)。见 Rust。 如果你啥都想要:性能,抽象,向后兼容性。那你就忘掉优雅吧,你会得到一个类似于 C++ 的东西。除非有人在 1980 之前能开天眼,那也许能设计出一个更优雅的东西。 但那也不会是 Rust——在 C++ 诞生的年代,Rust 的编译速度恐怕没有任何人能承担得起。 也不会是 Java。虚函数是有性能代价的,甚至是巨大的性能代价。Java 的虚拟机都是要用到 C++ 的。 另外,const 为什么要用 final 来修饰?我觉得 Java 的这个设计也很没道理。C++ 里 final 基本不需要,也是受了其他语言的影响才有的,并且是个说明符,不是关键字。我在项目里从来没写过 final(override 还是经常需要的)。
1 - 铿2022-02-11auto 和 for的语法糖
作者回复: 严肃地讲,语法糖对于易用性非常重要。
- 常振华2021-09-30唯一喜欢的易用性改造就是列表初始化,其它的反而增加了复杂度。
作者回复: 这也只是你的一家之言。大部分人最喜欢的是 auto。
1 - 2012008612020-09-04老师,你好,如何看移动构造函数和拷贝函数,是编译器提供的还是用户自己写的?
作者回复: 你自己提供了,编译器自然就不会提供了。头文件里瞟一眼啰。如果是别人提供的类定义,从使用者的角度,不去看类定义,应该是无从分辨的。 从使用者的角度,容易分辨的,只能是用户不提供这些特殊成员函数时,编译器有没有自动提供。使用一下,不报错,自然就是编译器自动提供了。
- 赵国辉2020-07-22老师,有没有对c++虚函数表实现原理讲解比较透彻的文章或者书籍推荐一下。对这方面比较感兴趣。
作者回复: 直接搜索就好。我看结果很匹配的。
- tr2020-03-01第四版中文版中给出的代码是这样的: constexpr int ipow(int x, int n) { return (n > 0) ? x * ipow(n - 1) : 1; } template<char c> constexpr int b3_helper() { static_assert(c < '3', "not a ternary digit"); return c; } template<char c,char ... tail> constexpr int b3_helper() { static_assert(c < '3', "not a ternary digit"); return ipow(3, sizeof...(tail)) * (c - '0') + b3_helper(tail...); } template<char...chars> constexpr int operator"" _b3() { return b3_helper(chars...); }展开
作者回复: 嗯,这明显是代码后来改过了。初版的代码应该没有好好测过,问题还不止一个。 顺着原先代码的思路也能实现的,但应该是下面这个样子(可参考第 15 讲): #include <stdexcept> constexpr int ipow(int x, int n) { return n > 0 ? x * ipow(x, n - 1) : 1; } template <typename C> constexpr int b3_helper(C c) { if (c < '0' || c >= '3') { throw std::invalid_argument("not a ternary digit"); } return c - '0'; } template <typename C, typename... Tail> constexpr int b3_helper(C c, Tail... tail) { if (c < '0' || c >= '3') { throw std::invalid_argument("not a ternary digit"); } return ipow(3, sizeof...(tail)) * (c - '0') + b3_helper(tail...); } template <char... chars> constexpr int operator"" _b3() { return b3_helper(chars...); } 第一个 b3_helper 还可以进一步精简成: constexpr int b3_helper() { return 0; }
共 3 条评论 - tr2020-02-29是19.2.6节三进制的那个例子,b3_helper的调用有问题,vs2019社区版报的是“应输入0个参数,却提供了3个”,但是把圆括号换成模板参数的尖括号又报告“未找到匹配的重载”。像这种怎么修改合适呢?
作者回复: 我不知道你看到的是什么,我这边是编译通过了。完整代码如下: #include <iostream> constexpr int ipow(int x, int n) { return n > 0 ? x * ipow(x, n - 1) : 1; } template <char...> struct helper; template <char c> struct helper<c> { static_assert( '0' <= c && c < '3', "not a ternary digit"); static constexpr int value() { return c - '0'; } }; template <char c, char... tail> struct helper<c, tail...> { static_assert( '0' <= c && c < '3', "not a ternary digit"); static constexpr int value() { return (c - '0') * ipow(3, sizeof...(tail)) + helper<tail...>::value(); } }; template <char... chars> constexpr int operator"" _b3() { return helper<chars...>::value(); } int main() { std::cout << 201_b3 << std::endl; }
共 3 条评论 - tr2020-02-29老师,本贾尼在他的《c++程序设计语言》中480页写了一种叫模板字面值常量的语法,不过他的代码编译不过。如果您有时间的话能不能帮忙看下是不是真的有这种写法还是说其他什么原因
作者回复: 我没有中文版,我也不确定你说的是哪个版本。 我看到第四版的19.2.6节似乎有类似你说的东西。请详细说一下你指的是哪一节的哪个例子,使用什么编译器,具体错误信息是什么。 单独起一个新的评论,不要回复这一个。
- 皮皮侠2020-02-26用得最顺手的就是在for循环里auto,override在Qt里继承类重写虚函数时用到。其他的default和delete以前初学时写过几个,现在在项目里倒用得少了,以后尽量多用多试,毕竟不影响性能!谢谢老师解析了这么多C++新特性;)
作者回复: 最方便的确实是在循环里用 auto,尤其在基于范围的循环里。其他的也可以慢慢用起来。
- zKerry2020-02-24呃,这些东西在c#里都有
作者回复: 首先,语言之间互相学习是正常的。 其次,C# 里有数字分隔符和二进制字面量是在 C# 7.0。换句话说,晚于 C++14。 另外,我不知道 C# 有静态断言和自定义字面量的支持。
- 中年男子2019-12-24既然有了这些特性, 我觉的就得在平时开发中用起来,不用就没有用,完全浪费了大神的研究
作者回复: 对的,一定要用起来!