使用 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
