这种使用虚拟保护方法扩展库的方法安全吗?
Posted
技术标签:
【中文标题】这种使用虚拟保护方法扩展库的方法安全吗?【英文标题】:Is this way to extend a library with virtual protected methods safe? 【发布时间】:2015-04-02 09:08:56 【问题描述】:我正在使用的外部库具有以下结构:
#include <stdio.h>
struct A
protected:
virtual void f()
;
struct B : A
void f() printf("B\n");
;
我现在已经扩展了这个库
struct C : A
void f() printf("C\n");
;
现在我想要一个struct D : A
,它使用B
或C
的f()
,具体取决于运行时可用的信息。我无法修改库,并且从B
继承C
是不切实际的,因为B
比C
复杂得多。这就是我想出的:
struct _A : A
// bypass protected
inline void _f() f();
;
struct D : A
D(char x)
switch (x)
case 'B': p = (_A*) new B(); break;
case 'C': p = (_A*) new C(); break;
~D() delete p;
void f() p->_f();
_A* p;
;
int main()
D b('B'), c('C');
b.f();
c.f();
我在 MacOSX 上对其进行了测试,它在 g++ 和 clang++ 上都能正常工作。但是一般来说安全吗?如果没有,有更好的方法吗?
【问题讨论】:
B
、C
和 _A
不相关。试图将一个视为其他之一是未定义的行为。
【参考方案1】:
不,你所拥有的并不安全。 B
和 C
不继承自 _A
,因此将它们视为未定义行为。它可能会起作用,它可能会崩溃,它可能会在线订购披萨,这一切都取决于当前的月相。所以不要这样做。
而且我相信您不必这样做。以下应该有效:
struct BB : B
using B::f; // Make it public
;
struct D : A
D(char x)
switch (x)
case 'B': b.reset(new BB()); break;
case 'C': c.reset(new C()); break;
void f()
if (b) b->f();
else c->f();
std::unique_ptr<BB> b;
std::unique_ptr<C> c;
;
这个想法是最多保持一个指针非空(或者找到另一种方法来确定您是否有 BB
或 C
— boost::variant
也可能有用)。
另请注意,名称_A
对于用户代码是非法的。以下划线后跟大写字母开头的标识符是为编译器和标准库保留的。
【讨论】:
【参考方案2】:不,不是。
您正在将 B 转换为 _A,这可能会在此过程中发生变化。当前 _A 与 A 相同的事实只是巧合,您不能依赖。
如果您的目标是访问受保护的函数,您可以使用 pImpl 方法:
struct _Abstract
virtual void doF()=0;
struct _B : B, _Abstact
void doF()f();;
struct _C : C, _Abstract
void doF()f();;
struct D
D (_C* impl)
pImpl = impl;
D (_B* impl)
pImpl = impl;
void f() pImpl->dooF();;
private:
_Abstract* pImpl;
那么你可以拥有
D* b = new D(new _B());
D* c = new D(new _C());
b->f();
c->f();
【讨论】:
【参考方案3】:@MichaelCMS 的回答似乎是最好的,因为从_Abstract
继承使我粗略的演员表正式正确。此外,与@Angew 的回答相反,如果有很多类,如B
和C
,它可以很好地扩展。我不得不稍微修改它以适用于我的示例:
struct _Abstract
virtual void _f() = 0;
virtual ~_Abstract()
;
template <class T>
struct _A : T, _Abstract
// bypass protected
void _f() T::f();
;
struct D : A
D(char x)
switch (x)
case 'B': p = new _A<B>(); break;
case 'C': p = new _A<C>(); break;
~D() delete p;
void f() p->_f();
_Abstract* p;
;
显然,如果图书馆设计师将f()
公开,整个问题就会消失...所以,图书馆设计师,请公开您的方法!您无法预测所有用例,您只是在强迫我们通过或多或少粗略的方法绕过您的受保护(甚至是私人)......
编辑
在实践中,双重继承解决方案效果不佳,因为它在我的案例中导致了钻石继承问题,这使事情变得非常复杂。受到@Charles Bailey 对Accessing protected member of template parameter 的回答的启发,我想出了这个:
struct U : A
typedef void (A::*f_t)();
static inline f_t _f() return &U::f;
;
struct D : A
D(char x)
switch (x)
case 'B': p = new B(); break;
case 'C': p = new C(); break;
~D() delete p;
void f() (p->*U::_f())();
A* p;
;
这直接从根本上解决了问题,即方法被设置为受保护而不是公共的,同时不会不必要地使继承情况复杂化。由于这只是通过成员函数指针删除受保护属性的技巧,因此它应该是安全的:-)
【讨论】:
以上是关于这种使用虚拟保护方法扩展库的方法安全吗?的主要内容,如果未能解决你的问题,请参考以下文章