使用 Pimpl 很多时候是必须的,ABI 兼容的时候基本都要靠它。在 C++ 11 当中,通过 default member initializer 可以实现很多有趣的效果,加上宏的话很多时候可以利用来实现 Metadata 的效果,例如:
#define PROPERTY(NAME) \ Property NAME{this, #NAME}; class Registry { friend class Property; public: int& getValueByName(const std::string &name) { return *values_[name]; } protected: void registerValue(const std::string name, int* value) { values_[name] = value; } std::unordered_map<std::string, int*> values_; }; class Property { public: Property(Registry* r, const std::string& name) { r->registerValue(name, &value_); } operator int() { return value_; } Property& operator=(int v) { value_ = v; return *this; } private: int value_; }; class MyRegistry : public Registry { public: PROPERTY(a); PROPERTY(b); }; int main() { MyRegistry r; r.getValueByName("a") = 1; r.b = 2; return r.a + r.getValueByName("b"); }
但是实际上这还是需要定义新的 member,如果将来有了新的 member 就没法保证 Binary compatibility 了,假设我们要把 MyRegistry 转变成 Pimpl 怎么办呢?简单实现一下的话就会变成这样:
class MyRegistryPrivate { Property a{???, "a"}; };
那么 ??? 处传什么才好呢?
你可能会想到把 MyRegistry 的 pointer 传给 Pimpl 不就行了吗?但是不要忘了,我们还依旧希望通过 default member initializer 的形式来定义。传过来的参数之后即使直接去 initialize,也是在 default member initializer 之后才进行了。也就是说这样是不行的:
class MyRegistryPrivate { public: MyRegistryPrivate(MyRegistry *q) : q_ptr(q) {} MyRegistry *q_ptr; Property a{q_ptr, "a"}; };
也就是说要保证在更早的时候初始化 q_ptr,可以用一层继承来保证这点。
template class QPtrInit { public: QPtrInit(T *q) : q_ptr(q) { } protected: T* q_ptr; }; class MyRegistryPrivate; #define PROPERTY_PRIVATE(NAME) \ Property NAME{q_ptr, #NAME}; class MyRegistryPrivate : QPtrInit { public: MyRegistryPrivate(MyRegistry* q) : QPtrInit(q) {} PROPERTY_PRIVATE(a); PROPERTY_PRIVATE(b); };
完整例子参见:http://cpp.sh/4bhvh