Open dustpg opened 6 years ago
这里列出了自己常用的一些c/c++小技巧, 有些会有不足, 可以简单探讨一下.
分类: 小技巧
同理可以用于其他位, 比如16位什么的. 由于不同位的平台指针的大小可能是不同的, 所以导致一些逻辑必须分别讨论.
很多时候我们可能不会在意是移动平台还是桌面平台, 但是肯定会在意指针的大小. c++的话可以使用模板特化方便地处理, 模板特化也是c非常难以模拟的特性之一.
最简单的, 比如我们想在32位平台是用单精度浮点而64位平台使用双精度浮点:
template<int T> struct float_helper_t{ }; template<> struct float_helper_t<4> { using float_t = float; }; template<> struct float_helper_t<8> { using float_t = double; }; using mfloat_t = float_helper_t<sizeof(void*)>::float_t;
分类: 隐藏实现, 零代价
pimpl很好用, 但是不是零代价的. 不过对象大小是在编译器是固定(c++), 我们可以利用c++11的std::aligned_storage创建一个零代价的pimpl. 同时针对不稳定API可以用static_assert进行编译期断言.
std::aligned_storage
static_assert
例如WinAPI有一个SRWLOCK, 表面上是一个指针. 虽然我们可以用指针重解释, 但是作为例子可以这么实现:
SRWLOCK
// 头文件 namespace detail { template<size_t> struct rwlocker_impl_info {}; template<> struct rwlocker_impl_info<4> { enum { size = 4, align = 4 }; }; template<> struct rwlocker_impl_info<8> { enum { size = 8, align = 8 }; }; } class CRWLocker { enum { buf_size = detail::rwlocker_impl_info<sizeof(void*)>::size }; enum { buf_align = detail::rwlocker_impl_info<sizeof(void*)>::align }; protected: std::aligned_storage<buf_size, buf_align>::type m_impl; }; // 源文件 // 最好进行编译期断言 CRWLocker::CRWLocker() noexcept { // WinAPI 的SRWLOCK using ui_rwlocker_t = SRWLOCK; static_assert(sizeof(ui_rwlocker_t) == buf_size, "must be same"); static_assert(alignof(ui_rwlocker_t) == buf_align, "must be same"); const auto locker = reinterpret_cast<ui_rwlocker_t*>(&m_impl); ::InitializeSRWLock(locker); }
对于不稳定的API, static_assert是非常重要的.
分类: 实现技巧
基础数据结构中, 链表由于是指针的重要体现, 可以非常方便地处理多态:
------ ------ ------ node --> node --> node ------ ------ ------ data#1 data#2 data#3 ------ ------ ------
例如比较常用的"工厂模式"创建的各个对象可以用链表串起来:
struct Node { Node* prev; Node* next; }; struct Factory { Factory(); Node head; Node tail; }; struct Obj1 : Node { int a; }; struct Obj2 : Node { float a; }; Factory::Factory() { head.prev = nullptr; head.next = &tail; tail.prev = &head; tail.next = nullptr; }
每次添加节点可以在Factory::tail.prev处做文章. 删除节点由于有头节点与尾节点的存在非常简单:
Factory::tail.prev
node.prev->next = node.next; node.next->prev = node.prev;
多态的实现, 一般来说就是c++使用的虚函数. 不过注意的是虚表指针会占用一个指针的空间, 所以和节点的布局可以有两种:
A { vtable*; node; }; B { node; vtable*; };
一般选用A模式, B比较难实现. A模式又有一个面向对象常有的问题: 包含, 还是继承?
c++有一些自己不喜欢的东西, 这些东西都是属于, 对程序猿隐藏. A模式使用继承的话, static_cast转换Node和继承类会隐含一个偏移判断, 这个隐藏没有问题. 问题是转换前会对指针进行判断, 如果是nullptr的话, 转换后还是nullptr, 这个很合理但是自己不喜欢, 添加了一个隐藏的分支.
static_cast
nullptr
所以自己可能会使用"包含"模式, 再使用offsetof进行手动转换, 虽然offsetof对于非标准布局是UB行为, 但是实际上不是offsetof是UB, 而是非标准布局.
offsetof
自己常用的小技巧
这里列出了自己常用的一些c/c++小技巧, 有些会有不足, 可以简单探讨一下.
32位/64位等 分类
分类: 小技巧
同理可以用于其他位, 比如16位什么的. 由于不同位的平台指针的大小可能是不同的, 所以导致一些逻辑必须分别讨论.
很多时候我们可能不会在意是移动平台还是桌面平台, 但是肯定会在意指针的大小. c++的话可以使用模板特化方便地处理, 模板特化也是c非常难以模拟的特性之一.
最简单的, 比如我们想在32位平台是用单精度浮点而64位平台使用双精度浮点:
零代价pimpl
分类: 隐藏实现, 零代价
pimpl很好用, 但是不是零代价的. 不过对象大小是在编译器是固定(c++), 我们可以利用c++11的
std::aligned_storage
创建一个零代价的pimpl. 同时针对不稳定API可以用static_assert
进行编译期断言.例如WinAPI有一个
SRWLOCK
, 表面上是一个指针. 虽然我们可以用指针重解释, 但是作为例子可以这么实现:对于不稳定的API,
static_assert
是非常重要的.链表多态
分类: 实现技巧
基础数据结构中, 链表由于是指针的重要体现, 可以非常方便地处理多态:
例如比较常用的"工厂模式"创建的各个对象可以用链表串起来:
每次添加节点可以在
Factory::tail.prev
处做文章. 删除节点由于有头节点与尾节点的存在非常简单:多态的实现, 一般来说就是c++使用的虚函数. 不过注意的是虚表指针会占用一个指针的空间, 所以和节点的布局可以有两种:
一般选用A模式, B比较难实现. A模式又有一个面向对象常有的问题: 包含, 还是继承?
c++有一些自己不喜欢的东西, 这些东西都是属于, 对程序猿隐藏. A模式使用继承的话,
static_cast
转换Node和继承类会隐含一个偏移判断, 这个隐藏没有问题. 问题是转换前会对指针进行判断, 如果是nullptr
的话, 转换后还是nullptr
, 这个很合理但是自己不喜欢, 添加了一个隐藏的分支.所以自己可能会使用"包含"模式, 再使用
offsetof
进行手动转换, 虽然offsetof
对于非标准布局是UB行为, 但是实际上不是offsetof
是UB, 而是非标准布局.