一个关于 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).

This site uses Akismet to reduce spam. Learn how your comment data is processed.