一个关于 Pimpl 的小技巧

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

This entry was posted in Linux and tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

Note: Commenter is allowed to use '@User+blank' to automatically notify your reply to other commenter. e.g, if ABC is one of commenter of this post, then write '@ABC '(exclude ') will automatically send your comment to ABC. Using '@all ' to notify all previous commenters. Be sure that the value of User should exactly match with commenter's name (case sensitive).