作者loveme00835 (发箍)
看板C_and_CPP
标题Re: [问题] 请问我这个程式能用回圈做吗?
时间Sat Apr 4 14:43:08 2020
※ 引述《nullpointerk (打滚猫)》之铭言:
: 完成後,下面的程式码就都可以运作了,在浏览器上跑跑看吧
: https://godbolt.org/z/wrd7Rv
: 这样就优雅解决原 PO 的烦恼了:如果以後要读更多ID我就无解了
看到很恐怖的 code, 推文可能不是那麽清楚就直接发一篇了
你的程式码有几个问题, 有大有小, 後面我列举出来说明.
namespace collision
一般在开发的时候应该避免把东西放进命名空间 std 里, 这可能导
致 resolve name 时会模棱两可. 再来因为参数型别本身不定义在
std 内, 所以无法用
ADL (
Argument-
Dependent
Lookup) 来找到这
个函式
namespace std {
std::string to_string(
const std::string::value_type c)
{
return {c}; }
}
// namespace std
比较好的做法就是呼叫的时候不使用 qualified name, 透过
using 把候选名单拉进来
using std::to_string, ::to_string;
auto to = to_string(from);
在使用 std::begin() / std::end() 等函式的时候也是一样, 为了
保有更改容器的弹性, 我们不会用 qualified name, 而是先把可能
的名称先 using 进来, 编译器首先会透过 ADL 找寻可用的版本 (
最好是 overloading), 找不到才会回来用 std 底下的模板来具现
化呼叫实体 (因为後者优先权低).
不过在 C++20 以後开始全面引进
CPO (
Customization
Point
Object) 的概念, 上述所提的 std 函式将会提升为 functor, 在它
们的 call operator 内还是会透过 ADL 找寻呼叫实体, 在那之後
是不是用 qualified name 就不是那麽重要了.
滥用 uniform initialization
uniform initialization 原本是用来消歧异的手段, 但在错误的情
境下使用反而会产生语义不明, 例如和 list initialization 混淆
. 如果类别也允许使用 std::initializer_list 来做初始化, 那麽
在接受多个引数的时候你怎麽知道被呼叫的建构子是哪版? 这在
C++17 引进
CTAD (
Class
Template
Argument
Deduction) 之後让
情况更恶化了, 而且 uniform initialization 不允许
narrowing
conversion, 搞清楚意图再使用会比较好.
Barry Revzin: Uniform initialization isn’t
https://bit.ly/2X91QDS
顺带一提, 你可以加上编译器选项
--pedantic-errors 看看有无误
解使用方法.
滥用 temporary object lifetime extension
根据
[class.temporary] 2 的描述:
2. The materialization of a temporary object is generally
delayed as long as possible in order to avoid creating
unnecessary temporary objects. [ Note: Temporary
objects are materialized:
(2.1) when binding a reference to a prvalue
这边其中一项就是绑定参考的情况, 但这仅限於直接绑定,
间接绑
定是不会获得再延长效果的. 在这里不得不提设计不良的
std::min() 它的宣告如下:
template<
class T >
const T& min(
const T& a,
const T& b );
因为参数型别是 ref to const, 所以可以接受 prvalue 做为引数,
而回传值只是把 ref 再传出去而已, 这里的用法和 ?: 运算子差不
多, 不过因为引数的 lifetime 只被延长到函式呼叫结束为止, 所
以回传值其实是
dangling reference. 而且在 C++17 以後保证了
copy elision, 所以已经不需要刻意这样做.
滥用 decltype
虽然 decltype 运算子好像可以省下未来修改程式码的功, 但有没
有想过 decltype 推导出来的型别如果是 ref to const 该怎麽办?
有没有用
type traits 来过滤掉不合法的情况?
reference to small object
reference 通常会以类似 pointer 的方式去实作, 当呼叫函式使用
pass by reference 传递引数的时候首先要考虑的是: 它的成本是
否低於 pass by value? 在
C++ Core Guidelines 有给出一个参考
F.16: For “in” parameters, pass cheaply-copied types by
value and others by reference to const
https://bit.ly/39AE728
为了 8 位元物件参数用 pass by reference 是显然不合理的.
意义不明的 forwarding reference
函式 unkonwn_logic_a() 里的参数 ids 型别被定义为 forwarding
reference.
template <
typename IDS>
std::string unkonwn_logic_a(
decltype(length(std::declval<IDS>())) pcs,
IDS &&ids
);
通常我们使用 fwding ref 是用来保留引数的
值类别 (value
category), 接着用同样的类别来选择可用的多载函式呼叫,
如 length(ids) 呼叫. 不过因为 ref 被绑定之後会变 lvalue ref
, 这里还需要使用
std::forward() 把值类别加回来才行. 所以正
确的呼叫应为 length(std::forward<IDS>(ids)); 但因为你压根没
有提供其他接受 rvalue 版本的 length() 函式, 所以在这里用
fwding reference 是多余的.
後话
通常我们在写类别/函式模板时, 情况都是:
因为有复数个实作非常相近,
所以把它们合并在一起
而不是
为了泛用而去写模板, 这容易因为没仔细检查 type
contraint 而写出很难改的模板. 有个准则就是你有没有透过
SFINAE (
Substitution
Failure
Is
Not
An
Error) 来限缩模板可
用情境. 因为模板内的操作都是基於某种假设而实作, 像是需要有
可被呼叫的成员函式等等, 不符合假设的型别本就不该拿来具现化
造成编译错误.
有一个可能比较少用的标头挡
<iterator> 里面像是 std::size()
就跟你的 length() 角色是一样的, 在实作之前看看标准函式库的
例子就能多少避掉上面提到的问题.
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 223.136.172.53 (台湾)
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/C_and_CPP/M.1585982593.A.3A8.html
※ 编辑: loveme00835 (223.136.172.53 台湾), 04/04/2020 15:21:45
1F:推 sarafciel: 推,放个假回来就看到两篇好文章 04/06 00:59
2F:推 flysonics: 推 04/06 18:57
3F:推 s4300026: 推 04/07 08:27