Open dustpg opened 6 years ago
这里列出了自己常用的一些c/c++小技巧, 有些会有不足, 可以简单探讨一下.
分类: 标准
这个是一个几乎为标准的一个东西, c++端将所有函数声明为纯虚函数, 而且不含成员变量, 如:
#if defined(_MSC_VER) && defined(__cplusplus) #define NOVTABLE __declspec(novtable) #else #define NOVTABLE #endif struct NOVTABLE IObject { // 释放接口 virtual void Dispose() = 0; };
类微软编译器所实现的虚函数为虚表以兼容微软编译器(COM组件的实现), 同时微软编译提供了一个优化扩展语句__declspec(novtable)为不需要虚表的类, 不会创建虚表, 以减小程序体积.
__declspec(novtable)
c方面则没有标准, 可以像虚表一样实现以兼容:
typedef struct { // 释放接口 void(*dispose)(void* user_ptr); } object_vtable_t; typedef struct { // 释放接口 object_vtable_t* vtable_ptr; } object_t;
或者直接使用函数指针:
typedef struct { // 释放接口 void(*dispose)(void* user_ptr); // 用户数据 void* user_ptr; } object_t;
这样的目的, 用面向对象那一套说就是多态. 还有的, 用自己的话就是'自定义', '自定义'部分推荐使用虚函数实现, 因为编译器会发现只有一个实现而可能会直接调用(激进型优化).
分类: 减少分支
常见于c库, 因为一般实现为直接使用函数指针:
typedef struct { // 输出数据 void(*output)(void* user_ptr, const output_t* data); // 用户数据 void* user_ptr; } object_t;
很多库会有, 或者说目的就是"输出数据". 比如音频解码之类的, 我们输入原始数据, 解码库输出数据.
但是有可能输出数据的格式每次会不一样, 我们可以在函数里面判断格式再跳转分支. 或者, 判断格式后直接更换接口, 以减少分支跳转.
分类: 提高效率
这是自己"自定义"的东西了, 在接口中, 可能需要实现一些'get'操作, 如果'get'是一个简单的return的话, 自己称之为"不划算", 本来可以简单获得的东西没必要单独用虚函数, 浪费空间与时间. 例如:
return
struct IStream1 { // 读取流 virtual int Read(void*, int) = 0; // 获取流长度 virtual int GetLength() const = 0; }; struct XStream2 { // 读取流 virtual int Read(void*, int) = 0; // 获取流长度 int GetLength() const { return m_length }; protected: // 流长度 int m_length; };
如果是内部接口的话, 自己还会简单写为:
struct XStream3 { // 读取流 virtual int Read(void*, int) = 0; // 流长度 int length; };
一方面这多亏了c++允许多继承, 不过实际上多继承还是用的不多, 一般还是单继承.
当然, 这个只适合简单return的'get', 复杂的不算. 复杂的函数用虚函数就比较"划算"了.
pimpl对于c++来说是一个小技巧, 对于c来说几乎没必要或者说实际上处处都能用上. 即 pointer to implement, 自己会酌情使用pimpl:
pointer to implement
// 头文件 struct CImpl; class CPimpl { CImpl* m_pImpl; }; // 源文件 // 具体实现 struct Impl int a; };
这样目的就是隐藏实现, 合理使用可以变相地提高编译速度.
分类: 隐藏实现, 提高编译速度
C++中, 如果是一个比较大的类, 自己会看情况声明一个private, public或者其他权限的::Private结构体:
class CControl1 { struct Private; }; class CControl2 { public: struct Private; };
这样主要是内层嵌套类允许访问外层的private部分, 自己会有两种用法, 一种就是和pimpl结合使用:
// 头文件 class CPimpl { struct Private; Private* m_pImpl; }; // 源文件 struct CPimpl::Private { // 具体实现 };
以及提高编译速度, 减少暴露细节:
试想一下, 如果要写一个大switch, 各个分支肯定是调用函数, 而不是直接写. 如果是这是一条类函数, 这时候就得修改该类的声明, 然后所有引用该头文件的源文件都必须重新编译一次.
switch
// 头文件 class CControl1 { int x; public: void OnCase0(); void OnCase1(); int GetX() const { return x; } }; // 源文件 void CControl1::OnCase0() { } void CControl1::OnCase1() { }
而这样实现则可以实现伪动态添加:
// 头文件 class CControl2 { int x; public: struct Private; }; // 源文件1 strtuct CControl2::Private{ static void OnCase0(CControl2&) { } }; // 源文件2 strtuct CControl2::Private{ static void OnCase1(CControl2&) { } static int& GetX(CControl2& x) { return x.x; } };
由于每个编译单元(源文件)是独立的, CControl2::Private允许在不同源文件声明地不一致. 这也极大地方便了我们.
CControl2::Private
分类: 编码技巧
如果自己的c++类存在真正的"继承"关系, 自己会在类声明的第一行(private区域)typedef/using一个Super类型指向基类(超类).
typedef/using
class A{ }; class B : public A { using Super = A; };
在自己所谓真正的"继承关系", 主要是会重写(override)基类的一条或多条虚函数. 某些情况下, 我们派生类需要的是"强化"基类虚函数, 而不是完全的重写基类. 这时候我们会调用基类的该条函数:
void B::Update() { // xxxxxx A::Update(); }
如果使用了private ::Super的话, 可以直接无脑:
void B::Update() { // xxxxxx Super::Update(); }
这样, 如果重定向B的基类, 只需要修头文件紧贴的两行, 后面的源文件不用修改.
再特殊一点, 如果实现类有一条函数是跳过基类的实现而调用高阶基类的实现:
class A{ }; class B : public A { using Super = A; }; class C : public B { using Super = B; };
然后
void C::Call() { Super::Call(); } void C::Update() { A::Update(); }
这样可以很明显知道C::Update是特殊实现, 提高了可读性.
C::Update
自己常用的小技巧
这里列出了自己常用的一些c/c++小技巧, 有些会有不足, 可以简单探讨一下.
接口
分类: 标准
这个是一个几乎为标准的一个东西, c++端将所有函数声明为纯虚函数, 而且不含成员变量, 如:
类微软编译器所实现的虚函数为虚表以兼容微软编译器(COM组件的实现), 同时微软编译提供了一个优化扩展语句
__declspec(novtable)
为不需要虚表的类, 不会创建虚表, 以减小程序体积.c方面则没有标准, 可以像虚表一样实现以兼容:
或者直接使用函数指针:
这样的目的, 用面向对象那一套说就是多态. 还有的, 用自己的话就是'自定义', '自定义'部分推荐使用虚函数实现, 因为编译器会发现只有一个实现而可能会直接调用(激进型优化).
更换接口
分类: 减少分支
常见于c库, 因为一般实现为直接使用函数指针:
很多库会有, 或者说目的就是"输出数据". 比如音频解码之类的, 我们输入原始数据, 解码库输出数据.
但是有可能输出数据的格式每次会不一样, 我们可以在函数里面判断格式再跳转分支. 或者, 判断格式后直接更换接口, 以减少分支跳转.
混合接口
分类: 提高效率
这是自己"自定义"的东西了, 在接口中, 可能需要实现一些'get'操作, 如果'get'是一个简单的
return
的话, 自己称之为"不划算", 本来可以简单获得的东西没必要单独用虚函数, 浪费空间与时间. 例如:如果是内部接口的话, 自己还会简单写为:
一方面这多亏了c++允许多继承, 不过实际上多继承还是用的不多, 一般还是单继承.
当然, 这个只适合简单
return
的'get', 复杂的不算. 复杂的函数用虚函数就比较"划算"了.pimpl
分类: 标准
pimpl对于c++来说是一个小技巧, 对于c来说几乎没必要或者说实际上处处都能用上. 即
pointer to implement
, 自己会酌情使用pimpl:这样目的就是隐藏实现, 合理使用可以变相地提高编译速度.
::Private
分类: 隐藏实现, 提高编译速度
C++中, 如果是一个比较大的类, 自己会看情况声明一个private, public或者其他权限的::Private结构体:
这样主要是内层嵌套类允许访问外层的private部分, 自己会有两种用法, 一种就是和pimpl结合使用:
以及提高编译速度, 减少暴露细节:
试想一下, 如果要写一个大
switch
, 各个分支肯定是调用函数, 而不是直接写. 如果是这是一条类函数, 这时候就得修改该类的声明, 然后所有引用该头文件的源文件都必须重新编译一次.而这样实现则可以实现伪动态添加:
由于每个编译单元(源文件)是独立的,
CControl2::Private
允许在不同源文件声明地不一致. 这也极大地方便了我们.private ::Super
分类: 编码技巧
如果自己的c++类存在真正的"继承"关系, 自己会在类声明的第一行(private区域)
typedef/using
一个Super类型指向基类(超类).在自己所谓真正的"继承关系", 主要是会重写(override)基类的一条或多条虚函数. 某些情况下, 我们派生类需要的是"强化"基类虚函数, 而不是完全的重写基类. 这时候我们会调用基类的该条函数:
如果使用了private ::Super的话, 可以直接无脑:
这样, 如果重定向B的基类, 只需要修头文件紧贴的两行, 后面的源文件不用修改.
再特殊一点, 如果实现类有一条函数是跳过基类的实现而调用高阶基类的实现:
然后
这样可以很明显知道
C::Update
是特殊实现, 提高了可读性.