C_and_CPP 板


LINE

※ 引述《lovejomi (JOMI)》之铭言: : 文有点长 : 由於跟外国同事共同开发程式互相有code review. : 某位同事写的code已经有点超过了, 并且会干预其他人如果不是他那种style写法 会要求 : 改正 : 以下是 每一种写法 我标数字 目的是希望大家可以给我一些建议是不是他太超过还是我 : 还无法体会他的好 因为版本迭代速度太快, 除非直接看提案或参与 group meeting, 不然得到的资讯也许会过於片面, 导致多数情况是去跟随某些人的 偏好 (可能是 committee member) 却不是选择最适合的写法. 所以 首要的就是尽量提升对语言的理解度, 撰码的时候记住以下两个原 则: 1. 选择语意最精确的写法 2. 选择最不容易出错的写法 连猴子都可以写出符合标准的程式码; 但却很难写出看得舒服的程 式码, 而若要评断语言熟悉度, 就看一个人是否知道每种语言特性 的缺点, 这是提案才会特别着墨的地方. 如果旧的特性就能解决问 , 除非有其他不可抗力的因素, 我们不会用新的特性来写. What is the zero-overhead principle? https://bit.ly/3fL4qH1 以下会尽量说明原 PO 有提到的特性, 至於要如何选择其实算比较 客观没有争议的, 要由你的情境来决定. : 1. : auto v = Foo<int>{}; : auto v = vector<int>{}; : // 永远使用{}, {} 在container上很好读, 但他不管怎样一定是{}, ()已近乎消失 : // 永远auto = : // vector<int> v; 臭了吗.... : 我个人觉得不该滥用 "等号" : 我有用一些观点例如 : copy cstor被delete情况, 只是因为你现在用c++17才给过, 建议他可以考虑相容c++14 : 但也是被驳回 说 不需考虑. : int a = 1; 写成 int a{1}就很怪 : Foo f{1,2,3}; 会让我以为他提供initializer_list 的建构子 : 殊不知其实只是想呼叫 Foo(int,int,int)版本的, 这样写真的是被鼓励的吗? : 我觉得要变通而不是完全弃用 () 建构 用 {} 初始化将会保证不管是 class type 或 scalar type 都可以 得到适当的初始値 (value-initialized), 而最重要的是 list in- itialization 的引数传递不允许 narrowing conversion, 算是比 较严格的写法; 但是 auto 主要的用途是请编译器帮忙作 type deduction, 那这时的写法就会分成几种: auto v1 = std::vector<int>{}; auto& v2 = std::vector<int>{}; const auto& v3 = std::vector<int>{}; auto&& v4 = std::vector<int>{}; 那麽问题来了, 以上哪个才是语意最精确的写法? 其实是 v4 (只建 构一个物件, 没有任何复制). auto 可以让我们选择性地省下写型 别的工, 但有时却是不得不写 (例如 closure). 但用 auto 还会衍 生其他问题: 为了绑定 r-value 会作 lifetime extension. 而这 个性质会需要你特别留意物件生命周期, 除了没办法好好加上 const specifier 以外, 处理 proxy object 也要格外注意: auto&& v5 = std::identity()(std::vector<int>{}); // dangling reference auto bits = std::bitset<3>{5}; auto first_bit = bits[2]; bits[2] = false; assert(first_bit); // assertion failure : 2. 承上 : auto ptr = static_cast<Foo*>(nullptr); : 就是不肯 Foo*ptr = nullptr; : 甚至他写 : struct Data : { : auto A = std::string{}; : auto B = ENUM::X; : auto C = int{}; : auto id = static_cast<add_pointer<GUID>::type>(nullptr); : } : 这很夸张 auto 只允许在 static/const data member 上使用, 你确定这编译 得过吗? : 我对於struct肯定是不用auto : 甚至我想问各位 struct 每个element都给初始直 这是好的吗? : 对我来讲这是使用这struct的人的义务 : Data d{....给初始直} : or : d.A = : d.B = 一个一个给 : 不知道各位喜欢哪种 针对struct 上面的写法是初始化 (initialization), 下面的写法是赋値 (ass ignment), 而使用 list-initialization 对於未给初始値的成员来 说初始化方式依型别而有所不同, 这两者语意就有差, 不是可以比 较的东西. : 3. 承上 : auto p = std::add_pointer<void>::type{XXX}; : auto p = std::add_pointer<int>::type{...}; : 之前他因为不知道std有提供add_pointer, 还刻意写一个traits 为了写出这行 : int* p = ....; 竟然不是他脑中的首选....我实在无法理解 如果把 add_pointer_t<T> 写在左边呢? std::add_pointer_t<int&> p = nullptr; 这样和你写 int* 有什麽差别? : 4. 承上 : auto Foo(..............................................................) -> : void : auto Bar(..............................................................) -> : std::vector<...> : 永远都是auto -> type 的写法 : 甚至 : auto main(..) -> void : 这trailing return type我一直无法体会好处,除非要deduction不然到底优点是什麽? : 5. : auto const* p = ....; : 基本上这没问题 但是多数人都是const auto* p; 但她却坚持不follow多数 因为多数人都是写错的. 这问题会在多个 cv-qualifier 和 * 混用 的情况才会比较明显, 但那通常意味着抽象化不足, 需要用 using 改写. 关键字: east const vs west const : 6. : 大量使用3rd MACRO,让程式码呈现类似 : XXX_RETURN_YY_IF(Method()); : LOG_ERROR_IF(!rc); : auto XXX -> noexcept : TRY(); : CATCH_RETURN(); : THROW_IF(.....); : 只要他写的code都是这种长相的....说真的对我来讲好难读... : 甚至写一段程式没用到macro 还会担心是不是有macro可套 只要是使用巨集的地方, 都是可以透过前处理器来做抽换的, 譬如 用它同时支援新旧版的编译器: #if 201703L <= __cplusplus # define FALL_THROUGH [[fallthrough]] #else # define FALL_THROUGH #endif switch (i % 2) { case 0: break; case 1: FALL_THROUGH; default: break; } <cassert> 里的 assert() 也是一个例子. 相对其他写死的程式码 , 巨集能较好因应编译环境的变更. : 7. 坚持C++ exception 一定比error code来的好 : 所以要求团队有error都要用exception, 如果实作上用exception会不好设计的话请提出 : 来 : 当成特例来讨论 : 对於noexcept有没有加非常计较跟坚持 : 如果设计dll : errorcode dllexport... API() : { : try : { : auto rc = XXX(); : if(rc == FAILED) { throw yyyy; 让下面接} : return success; : } : catch(...) : { : return yyy; : } : } : 为了用exception....但又不能往dll外丢 竟然自丢自接...无法理解 关於 exception 还有 error code 的论战已经有很多了, 除了要考 虑错误发生时的状态是否合法以外, 能不能有某种程度上的回传值 也是介面设计的重点. 比较新的观念是利用 std::expected 揉合两 者的优点, 但这其实比较吃开发团队的风格. : 8. : std::size() std::data() std::begin() std::end() : 只要用了 : type.size() type.data() type.begin type.end都会被逼着改... : 我想说的是 如果写template code当然用std::xxx会更generic....但不是, 都是在非te : mplate情形,用自己member 合情合理(是不是可以减少compile 时间,因为不用产生tem : plate程式码?) 考虑以下程式码把 range 里的所有整数元素相加并把结果回传: using range_type = std::conditional_t< true, // error if false std::vector<int>, int[10] >; int sum(const range_type& range) { return std::accumulate(range.begin(), range.end(), 0); } range_type r = { 1, 2, 3 }; sum(r); 重点会在你容不容易抽换实作型别, 和是不是模板没有关系. 使用 的容器也不需要让 std::begin() 呼叫合法. : 9. : 写出 : std::chrono::.... : 会被要求改成namespace chrono = std::chrono : 这我有点傻眼 写std::不是明确又更好理解吗? 假如我想要用自己的 clock 型别, 我可以建立一个 namespace 把 它定义在里面, 其他缺少的部分就拿别人的来补: namespace chrono { struct system_clock {}; using steady_clock = std::chrono::steady_clock; using high_resolution_clock = boost::chrono::high_resolution_clock; } 多一个间接层可以大大地增加实作弹性 : 10. : template<class T> : class Foo : { : void Bar(T&& t){ : Baz(std::forward<T>(t)); : } : }; : 坚持说是用forward, 给他很多例子跟gcc vector实作也无法接受... : 但因为结果论 是一样的效果,所以我说服失败,反倒是被质疑只写std::move是想少打字 : 吧? : 11. : class Foo : { : std::string s{}; : vector<int> v{}; : int a{}; : Type x{}; : }; : 这边要说的是....{}固然没问题, 但 不加不是更简洁好读? 考虑以下两种宣告方式: int a; int b{}; 请问两者的初始值为何? : int a{} 为什麽就是不肯 = 0? 甚至 有时候会写 int a{0}; 一样是语意问题. int a = 0; 包含了可以将整数常数隐式转换给目 标型别这个假设, 在等号左右型别 (不考虑 value category) 相同 的情况下, 呼叫的可能是 copy ctor 或是 move ctor; 其他情况则 是 conversion ctor, 用 C-style 的初始化方式你很可能不知道发 生什麽事情: std::string s = 0; 一般用 {} 都是避免编译器混淆, 大部份用小括号已经足够. : 12. 几乎写一般函数都写在header然後冠上inline(一看也觉得不可能inline成功的) : 理由说 有文章说让compiler自己决定能不能inline, 程式效能更好(成功算赚到). : class的话也是尽可能实作写header (反正内部的code, 不是要变成shared library) : 其实wiki也写了缺点,header only难道在非template library上有也是被鼓励的吗?( : 假设code size变大 不重要) : 13. 承上 : class Foo{ static int a; 坚持不写 一定要写 inline int a;} : 他认为的好处是 不用特别找cpp写定义, 更能贯彻header only 的写法 : 14. 因为会写windows平台的程式 : 他会把用到的win32 api都wrap一层 : 例如 : raii_handle : CreateThread(...) : { : auto h = ::Creathread(...) : THROW_IF(!h) : return h; : } 还是透过间接层保留实作弹性 : 之类的 方向是把win32 error code base的api变成exception based.... : C++真的exception是被鼓励的吗? 对我来看 B>Z阿... : 其实还有很多而且越读他的code会越多奇怪的坚持产生 : 例如 : return std::move(local var)... : 刚好vc似乎不会跳warning变成好像很难说服他改掉(我说这多余的,且限制最佳化了, : 但被无视) : 对方大方向是 : 大量使用auto , 增加"可读性", 读者or呼叫者不care型态 用auto完全的对他来讲好读 : (我完全相反 让我理解力大减 我还要多跳过去定义看型别 去思考是否有问题, : auto XXX(....很长)-> type , 我为了要看type我还要拖曳到右边看.) : 对方认定 : vector<int> v;是 c style 初始方法 要大家用C++ style : auto v = vector<int>{};写 : 对方非常爱贴文章 : 只要你提出相反意见他都可以拿文章来回 要我去看文章(还有所谓的AAA style....) : 对方是真的花心思会去follow youtube cppconf的talk.... : 但共识久了 会觉得对方 真的是教课书说什麽就什麽 而且似乎查资料只查他认同的 : 关键字很可能都是下 : "exception better than error code c++" 之类的找文章.... : 我不喜欢这种照本宣科的, 但只要他一贴文章大概就句点了 (又臭又长, 我也不想细看 : 反正用英文讲不赢) : 请各位提供一些意见 : 当然这些都是被网路上广泛讨论的topic...但这版似乎没特别针对这些来讨论 : 希望得到大家的回馈,有些也许真的是被鼓励的但我还没学到真谛 : 谢谢 简单说如果没办法让你少写程式码, 就是滥用语言特性. 至於 auto 的辨认方法, 假如你对 function resolution 稍微熟悉 一点, 可以尝试写一些小范例来搞坏它, 不改变程式码的前提下, 愈容易改变行为表示滥用得愈严重 (语意容易发生改变的地方). 范例: https://wandbox.org/permlink/RVIrUSTwhuMQnztV -- P1389R0: Guidelines for Teaching C++ to Beginners https://bit.ly/2GvDWKb SG20 Education and Recommended Videos for Teaching C++ https://www.cjdb.com.au/sg20-and-videos --



※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 123.193.76.216 (台湾)
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/C_and_CPP/M.1589306465.A.F3E.html
1F:推 LPH66: 补充 11: 一个用 = 写结果出问题的例子: #1PC34wLm 05/13 05:00
2F:→ LPH66: 这个例子还是因为 VC 不管标准才外显的问题 05/13 05:00
3F:推 yumekanau: 推推 macro因为最近在处理跨os / architecture 颇有 05/13 11:07
4F:推 lovejomi: std::add_pointer_t<int&> p = nullptr; 这写法比较好吗 05/13 15:42
5F:→ lovejomi: auto 只允许在 static/const data member<==打错 05/13 16:11
6F:→ lovejomi: 他都是static constexpr~ 05/13 16:11
type traits 的好处在於可以帮你处理掉 ref-to-ref 或是 ptr- to-ref 的情形. 通常开发上鼓励使用 type alias, 直接写死实际 型别是没有扩充性的做法. 在开发 C++ 程式的时候我们只在意这型 别能为我们做什麽 (constraint); 而不在意它是什麽, 一旦我们知 到实际型别, 我们很容易就写出过度耦合的程式码: using range_type = std::vector<int>; range_type v = { 3, 2, 1 }; std::sort(begin(v), end(v)); // error if use std::list C++ style 就是 generic programming, 这不是专指模板, 而是任 何情况下都应该考虑扩充性. 换句话来说可能今天你写了模板但这 个模板却一点也不 generic.
7F:→ lovejomi: 上述有道理...但我觉得对方没有这意思....说真的 05/13 18:22
8F:推 lovejomi: 补充问, auto&& v = std::identity()(Foo{}); 05/13 23:15
9F:→ lovejomi: 我想问 为什麽这样就没延长lifetime了呢? 05/13 23:15
10F:→ lovejomi: 因为return type不是 by value而是reference吗?根本问题 05/13 23:16
11F:推 lovejomi: @LPH66: https://bit.ly/3fHiem9 05/13 23:20
12F:→ lovejomi: 我用vs2019目前是预设不给过 以前不知道 05/13 23:21
13F:→ lovejomi: 但我想问 {}无疑可以防止一些错误, 但如果使用都正确 05/13 23:22
14F:→ lovejomi: {}真的是比较好的写法吗? 明确不是更好读? 或是用()古法 05/13 23:22
15F:→ lovejomi: struct{static constexpr auto xxx = .....;}也有好处吗 05/13 23:25
16F:推 LPH66: VC 近期的版本有比较照标准走了, 稍微早一点的版本才会这样 05/14 09:42
17F:→ MOONRAKER: 黑底黑字和黑底蓝字 不知道哪个比较恐怖。 05/14 15:18
对不取 Q_Q 下次换个 theme 好惹 ※ 编辑: poyenc (123.193.76.216 台湾), 05/14/2020 18:50:50
18F:推 yoco: 你的 theme 是用程式上色的吗? 05/15 23:34







like.gif 您可能会有兴趣的文章
icon.png[问题/行为] 猫晚上进房间会不会有憋尿问题
icon.pngRe: [闲聊] 选了错误的女孩成为魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一张
icon.png[心得] EMS高领长版毛衣.墨小楼MC1002
icon.png[分享] 丹龙隔热纸GE55+33+22
icon.png[问题] 清洗洗衣机
icon.png[寻物] 窗台下的空间
icon.png[闲聊] 双极の女神1 木魔爵
icon.png[售车] 新竹 1997 march 1297cc 白色 四门
icon.png[讨论] 能从照片感受到摄影者心情吗
icon.png[狂贺] 贺贺贺贺 贺!岛村卯月!总选举NO.1
icon.png[难过] 羡慕白皮肤的女生
icon.png阅读文章
icon.png[黑特]
icon.png[问题] SBK S1安装於安全帽位置
icon.png[分享] 旧woo100绝版开箱!!
icon.pngRe: [无言] 关於小包卫生纸
icon.png[开箱] E5-2683V3 RX480Strix 快睿C1 简单测试
icon.png[心得] 苍の海贼龙 地狱 执行者16PT
icon.png[售车] 1999年Virage iO 1.8EXi
icon.png[心得] 挑战33 LV10 狮子座pt solo
icon.png[闲聊] 手把手教你不被桶之新手主购教学
icon.png[分享] Civic Type R 量产版官方照无预警流出
icon.png[售车] Golf 4 2.0 银色 自排
icon.png[出售] Graco提篮汽座(有底座)2000元诚可议
icon.png[问题] 请问补牙材质掉了还能再补吗?(台中半年内
icon.png[问题] 44th 单曲 生写竟然都给重复的啊啊!
icon.png[心得] 华南红卡/icash 核卡
icon.png[问题] 拔牙矫正这样正常吗
icon.png[赠送] 老莫高业 初业 102年版
icon.png[情报] 三大行动支付 本季掀战火
icon.png[宝宝] 博客来Amos水蜡笔5/1特价五折
icon.pngRe: [心得] 新鲜人一些面试分享
icon.png[心得] 苍の海贼龙 地狱 麒麟25PT
icon.pngRe: [闲聊] (君の名は。雷慎入) 君名二创漫画翻译
icon.pngRe: [闲聊] OGN中场影片:失踪人口局 (英文字幕)
icon.png[问题] 台湾大哥大4G讯号差
icon.png[出售] [全国]全新千寻侘草LED灯, 水草

请输入看板名称,例如:WOW站内搜寻

TOP