Open dustpg opened 5 years ago
这里列出了自己常用的一些c/c++小技巧, 有些会有不足, 可以简单探讨一下.
分类: 小技巧
有一个可能很常用的操作, 或者说函数: 平均数. 通过基础数学我们可以知道(a+b)/2, 如果用c中无符号整型表示:
(a+b)/2
uint32_t avg(uint32_t a, uint32_t b) { return (a+b)/2; }
一般情况下没问题, 但是在两个数足够大时可能会产生溢出, 由于乘法分配律可知, 我们可以实现为:
uint32_t avg(uint32_t a, uint32_t b) { return a/2 + b/2; }
但是我们操作的是无符号整型, 两个奇数一操作最低位就被忽略了, 所以我们可以利用位操作, 再加上除以2可以化作右移一位, 我们可以实现为:
uint32_t avg(uint32_t a, uint32_t b) { return (a>>1) + (b>>1) + (a&b&1); }
现在我们可以推广至颜色上, 我们现在常用的是R8G8B8X8格式, X表示忽略或者表示不透明度. 如果现在我们规定低到高位分别是RGB, 最高8位忽略, 并假设为0:
#define R8G8B8_MASK_HI ((uint32_t)0x00FEFEFE) #define R8G8B8_MASK_LO ((uint32_t)0x00010101) uint32_t avg_r8g8b8(uint32_t a, uint32_t b) { return ((a&R8G8B8_MASK_HI)>>1) + ((b&R8G8B8_MASK_HI)>>1) + (a&b&R8G8B8_MASK_LO); }
再推广一下, 以后我们可能觉得24bit颜色太辣鸡, 推广到30bit. 我们现在使用R10G10B10X2格式, 这个就可以修改为:
#define R10G10B10_MASK_HI ((uint32_t)0x3FEFFBFE) #define R10G10B10_MASK_LO ((uint32_t)0x00100401) uint32_t avg_r10g10b10(uint32_t a, uint32_t b) { return ((a&R10G10B10_MASK_HI) >> 1) + ((b&R8G8B8_MASK_HI) >> 1) + (a&b&R8G8B8_MASK_LO); }
不过实际上主要是不透明度的原因, 这个方法在用于包含Alpha信息的颜色时, 会由于Alpha权重问题出现问题. 这个问题引申出一个"预乘Alpha"的概念, 自己被这个东西坑过, 真是记忆犹新。
分类: 优化技巧
这个技巧很简单, 将多次内存申请合并到一次, 优点为:
void fx0(void) { void* a = malloc(100); void* b = malloc(200); if (a && b) { // XXXXX } // 虽然free接受空指针, 实际上可能会有类似析构的操作, 还是需要判断 if (a) free(a); if (b) free(b); } void fx1(void) { void* a = malloc(100); if (a) { void* b = malloc(200); if (b) { // XXXXXXXXXXXXXX free(b); } free(a); } } void gx(void) { void* a = malloc(300); if (a) { void* b = (char*)a + 100; // XXXXXXXXXXXXXX free(a); } }
缺点, 不适合处理大空间申请, 比如申请两个250MB的内存最好分别申请, 不然地址空间上可能找不到500MB连续空间, 但是能找到两个250MB的连续空间.
还有一个小缺点就是, 现在的运行库配合IDE能够在越界后进行报错. 假如合并, 前者越界会写入下一段空间, 这个非常危险, 并且比较难调试(所以越界检测是非常重要的, 建议使用大量的断言进行处理).
虽然这个技巧很简单, 但是实际上需要直接申请两个独立部分可能比较少见, 常见是"隐式"地多次申请:
struct A { B* b = new(std::nothrow) B; C* c = new(std::nothrow) C; }; struct D { B* b = new(std::nothrow) B; }; int main() { A a; delete(new(std::nothrow) D); }
这里列举两种情况, A情况就是上述比较明显的, 拥有两次显式的new, 而D的情况则是隐式的两次. 有一点就是我们可以通过replacement new和直接调用析构函数进行内存的再利用, 但是非常注意的是内存对齐的情况, c++目前并没有将c11的aligned_alloc纳入标准(即便纳入按照微软的脾气还是不可能在VS上实现, 主要是允许free掉aligned_alloc的), 申请对齐的内存需要自己处理又无形地增加了复杂度(好在一般按照8字节对齐就行, 除了特殊要求).
A
new
D
replacement new
调用析构函数
aligned_alloc
free
有时会我们会经常的使用"悬浮"状态的对象, 就像D这种, 完全手动控制生命周期的, 其中隐含的多次申请依然可以合并, 只需要找准偏移量即可.
柔性数组, 或者说零长数组(Zero-Length Array) 其实就是这个的一个体现:
struct array_t { size_t length; char data[0]; };
其中类c++扩展是[0], c99标准是[]. c++中, 有时候为了避免兼容问题, 会不写成员变量, 取而代之的是成员函数:
[0]
[]
struct array_t { size_t length; char* data() { return reinterpret_cast<char*>(this+1) } };
分类: 内部优化
数组一般来说就是储存同一类型的数据, 如果需要多态, 又要保持随机访问可能的就是储存指针数组. 不过内部使用或者强制规定的话, 我们可以规定一个上限, 各个类型的较大值.
enum : size_t { THIS_MAX_SIZE = 256, THIS_MAX_COUNT = 16 }; class Factory { template<typename T> T* get_at(size_t i) { assert(i < m_count && "out of range"); return reinterpret_cast<T*>(m_buffer + THIS_MAX_SIZE * i); } size_t m_count; uint8_t m_buffer[THIS_MAX_SIZE*THIS_MAX_COUNT]; };
类似这样, 其实就是上一条: 合并申请. 然后手动控制对象构造(replacement new)与析构(调用析构函数), 对于"内部优化"来说, 是允许的.
自己常用的小技巧
这里列出了自己常用的一些c/c++小技巧, 有些会有不足, 可以简单探讨一下.
颜色混合
分类: 小技巧
有一个可能很常用的操作, 或者说函数: 平均数. 通过基础数学我们可以知道
(a+b)/2
, 如果用c中无符号整型表示:一般情况下没问题, 但是在两个数足够大时可能会产生溢出, 由于乘法分配律可知, 我们可以实现为:
但是我们操作的是无符号整型, 两个奇数一操作最低位就被忽略了, 所以我们可以利用位操作, 再加上除以2可以化作右移一位, 我们可以实现为:
现在我们可以推广至颜色上, 我们现在常用的是R8G8B8X8格式, X表示忽略或者表示不透明度. 如果现在我们规定低到高位分别是RGB, 最高8位忽略, 并假设为0:
再推广一下, 以后我们可能觉得24bit颜色太辣鸡, 推广到30bit. 我们现在使用R10G10B10X2格式, 这个就可以修改为:
不过实际上主要是不透明度的原因, 这个方法在用于包含Alpha信息的颜色时, 会由于Alpha权重问题出现问题. 这个问题引申出一个"预乘Alpha"的概念, 自己被这个东西坑过, 真是记忆犹新。
合并申请
分类: 优化技巧
这个技巧很简单, 将多次内存申请合并到一次, 优点为:
缺点, 不适合处理大空间申请, 比如申请两个250MB的内存最好分别申请, 不然地址空间上可能找不到500MB连续空间, 但是能找到两个250MB的连续空间.
还有一个小缺点就是, 现在的运行库配合IDE能够在越界后进行报错. 假如合并, 前者越界会写入下一段空间, 这个非常危险, 并且比较难调试(所以越界检测是非常重要的, 建议使用大量的断言进行处理).
虽然这个技巧很简单, 但是实际上需要直接申请两个独立部分可能比较少见, 常见是"隐式"地多次申请:
这里列举两种情况,
A
情况就是上述比较明显的, 拥有两次显式的new
, 而D
的情况则是隐式的两次. 有一点就是我们可以通过replacement new
和直接调用析构函数
进行内存的再利用, 但是非常注意的是内存对齐的情况, c++目前并没有将c11的aligned_alloc
纳入标准(即便纳入按照微软的脾气还是不可能在VS上实现, 主要是允许free
掉aligned_alloc
的), 申请对齐的内存需要自己处理又无形地增加了复杂度(好在一般按照8字节对齐就行, 除了特殊要求).有时会我们会经常的使用"悬浮"状态的对象, 就像
D
这种, 完全手动控制生命周期的, 其中隐含的多次申请依然可以合并, 只需要找准偏移量即可.柔性数组, 或者说零长数组(Zero-Length Array) 其实就是这个的一个体现:
其中类c++扩展是
[0]
, c99标准是[]
. c++中, 有时候为了避免兼容问题, 会不写成员变量, 取而代之的是成员函数:数组多态
分类: 内部优化
数组一般来说就是储存同一类型的数据, 如果需要多态, 又要保持随机访问可能的就是储存指针数组. 不过内部使用或者强制规定的话, 我们可以规定一个上限, 各个类型的较大值.
类似这样, 其实就是上一条: 合并申请. 然后手动控制对象构造(
replacement new
)与析构(调用析构函数
), 对于"内部优化"来说, 是允许的.