dustpg / BlogFM

Blog for Me
MIT License
155 stars 23 forks source link

自己常用的C/C++小技巧[2] #33

Open dustpg opened 6 years ago

dustpg commented 6 years ago

自己常用的小技巧

这里列出了自己常用的一些c/c++小技巧, 有些会有不足, 可以简单探讨一下.

32位/64位等 分类

分类: 小技巧

同理可以用于其他位, 比如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

分类: 隐藏实现, 零代价

pimpl很好用, 但是不是零代价的. 不过对象大小是在编译器是固定(c++), 我们可以利用c++11的std::aligned_storage创建一个零代价的pimpl. 同时针对不稳定API可以用static_assert进行编译期断言.

例如WinAPI有一个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处做文章. 删除节点由于有头节点与尾节点的存在非常简单:

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, 这个很合理但是自己不喜欢, 添加了一个隐藏的分支.

所以自己可能会使用"包含"模式, 再使用offsetof进行手动转换, 虽然offsetof对于非标准布局是UB行为, 但是实际上不是offsetof是UB, 而是非标准布局.