今天开始,「每日一题」栏目正式上线,我争取每天在朋友圈发一个问题,邀请大家讨论,然后在公众号里公布答案。
特此声明:这里的每日一题,是指每日最多一题,而不是每日至少一题 。
这是今天的问题:
我这里再详细描述一下:
首先有一个类A:
struct A {
private:
struct AImpl;
AImpl *impl;
};
还有一个类B:
struct B {
void func(A *a) {
impl->func(a);
}
private:
struct BImpl;
BImpl *impl;
};
在源文件中还有个BImpl类:
struct BImpl {
void func(A *a) {
///< 这里想要拿到a->impl,该怎么做
}
};
在上面的代码中,用到了pimpl模式,关于此模式的介绍可以看我的这篇文章[pimpl设计模式] 。
其中A类和B类是暴露给外部的文件,只暴露一些应该给外部调用的接口,然后所有实现都放在内部的Impl类中。而且Impl中还包含了很多没有对外暴露的功能。
所以这里的大体逻辑是:对外暴露一个指针,对内使用它的impl指针。
但这里有个问题:在上面的代码中,impl指针是private变量,一个对象指针传递出去,再传回内部,怎么拿到它的impl指针呢?
说到这里,可能有些朋友会说,不会有这个问题,出现这种问题肯定是设计的不合理,然而这里我们暂且不讨论设计层面的东西,单纯的看看怎么能够解决这个问题。(由于业务的需求复杂性,导致我不得不采用这种设计,当然,更多的应该是因为我水平有限,没有想到更好的设计。)
我发完这个问题后,有很多朋友都私聊我一起讨论,最终总结出了三种答案。
方案1:友元。
把B类设置为A类的友元,然后在B中可以获得A类的private成员,然后改变一下BImpl中func的参数,变成这样:
struct B {
void func(A *a) {
impl->func(a->impl);
}
private:
struct BImpl;
BImpl *impl;
};
struct BImpl {
void func(AImpl *a) {
///<
}
};
Impl侧接收Impl类型的指针,这样似乎更合理一些。
方案2:强转
A::AImpl* impl = (A::AImpl)((long)a);
因为A的impl是类A的第一个成员变量,所以类A的对象地址和成员变量impl的地址相同,利用此原则就可以直接把a指针强转成Impl指针。
这里可能大家还会有些疑问,如果类A中有虚函数表怎么办?
其实不会出现这个问题,因为pimpl模式本质就是为了隐藏实现而生。隐藏实现大体就两种方式,接口继承或者pimpl,用pimpl就没必要用继承,而用了继承就没必要用pimpl。
我们再假设A有虚函数表,也没啥太大问题,大不了判断一下A是否有虚函数(可研究下type_traits),然后改变一下指针偏移量再去做强转,这样也行。
方案3:还是强转,利用二维指针转成一维指针
原理和方案2类似。
贴一个大佬的原话:
考虑到机器有可能是32位或64位字长,long类型与机器字长是相同,所以先转为long指针,目的是为了取其值。
而此方案更妙。
A::AImpl* impl = *(A::AImpl**)a;
其实个人认为后两种方案没啥区别,大家怎么看?
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8