C++ 左箭头运算符(2016)

有趣且“邪恶”的 C++ 惯用法

  • 线程把一个自定义左箭头运算符看作既有趣、巧妙,又有点可怕。
  • 还提到了其他“邪恶”模式:调用 delete this、先手动调用析构函数再对 this 做 placement-new,以及侵入式引用计数。
  • 有人认为 delete this 对侵入式引用计数对象或类似 COM 的 Release 是可以接受的;也有人更喜欢用智能指针把这类模式隐藏起来。
  • 具备仿射/移动类型语言(例如 Rust)被提及为使“销毁 this”在概念上比手动内存 delete 更安全。

运算符重载:力量与滥用

  • 许多人认为运算符重载只要用得得体就没问题;滥用只是例外,不是常态。
  • 有争议或带有玩味的用法示例:
    • <</>> 表示流、网络消息和容器插入。
    • + 组合 GUI 小部件。
    • * 表示 RAII 守卫。
    • / 表示文件系统路径。
    • 诸如“analog literals”和类似箭头或蝌蚪的运算符等奇特构造。
  • 有人称赞某些库(例如线性代数库),认为运算符能大幅提升可读性。
  • 也有人认为运算符总体上是设计失误:语义可能性太多(溢出行为等),相比命名函数可读性更差。

Iostreams 与格式化

  • << 做 I/O 被批评为糟糕的设计,而且没有传播到标准库之外。
  • 一些人强烈偏好格式字符串 API(std::format/std::printprintf 风格,或 fmt 库),因为更易用且类型安全。
  • 反方观点:iostream 的问题大多与运算符重载本身无关。

函数对象、Lambda 与 operator()

  • 重载 operator() 历史上使函数对象/闭包成为可能;如今 lambda 已覆盖大多数用例。
  • 不过,自定义函数对象类型在某些模式中仍被认为是必要的(例如 std::function 的内部、多重重载访问器、带行为的析构函数)。

成员指针与委托

  • C++ 的成员指针被描述为令人困惑且由实现定义。
  • 一种观点最初(后来有所软化)把它们等同于“实例指针 + 函数”的一对;另一些人坚持它们不包含实例状态,只编码偏移和分发数据。
  • 与其他语言中的“delegate”或“fat pointer”机制(D、C#、Virgil、Delphi、Python)相比:
    • fat pointer 因简单和一等闭包而受到赞赏。
    • C++ 更轻量的模型则被辩护为更灵活、更便宜,符合“不为你不用的东西付费”,必要时再用 std::function 这种更重的抽象。
  • 围绕性能权衡、虚调用、内联和编译模型引发了争论。

关于 C++ 与运算符的总体感想

  • 一些评论者表达了对 C++ 的力量与创造性的喜爱;另一些则称其过于复杂,甚至是“滑稽”或“可憎”的。
  • 反复出现的张力在于:巧妙的表达力(自定义运算符、各种技巧)与长期代码可读性、可维护性之间的冲突。