这种用虚拟保护方法扩展库的方法安全吗?
Is this way to extend a library with virtual protected methods safe?
我正在使用的外部库具有以下结构:
#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()
。我无法修改库,并且 C
从 B
继承是不切实际的,因为 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
,因此像这样对待它们是未定义的行为。它可能会工作,可能会崩溃,可能会在线订购披萨,这一切都取决于当前的月相。所以不要这样做。
而且我相信您不必这样做。以下应该有效:
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
对于用户代码来说是非法的。以下划线开头后跟大写字母的标识符是为编译器和标准库保留的。
不,不是。
您正在将 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();
@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()
public,整个问题就会消失......所以,库设计者,请留下你的方法 public!您无法预测所有用例,您只是在迫使我们通过或多或少粗略的方法绕过您的受保护(甚至私有)...
编辑
在实践中,双重继承解决方案的效果并不好,因为在我的案例中它导致了菱形继承问题,这让事情变得非常复杂。受到@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;
};
这直接从根本上解决了问题,即方法被设置为受保护而不是 public,同时不会不必要地使继承情况复杂化。由于这只是通过成员函数指针删除受保护属性的技巧,因此应该是安全的:-)
我正在使用的外部库具有以下结构:
#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()
。我无法修改库,并且 C
从 B
继承是不切实际的,因为 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
,因此像这样对待它们是未定义的行为。它可能会工作,可能会崩溃,可能会在线订购披萨,这一切都取决于当前的月相。所以不要这样做。
而且我相信您不必这样做。以下应该有效:
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
对于用户代码来说是非法的。以下划线开头后跟大写字母的标识符是为编译器和标准库保留的。
不,不是。
您正在将 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();
@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()
public,整个问题就会消失......所以,库设计者,请留下你的方法 public!您无法预测所有用例,您只是在迫使我们通过或多或少粗略的方法绕过您的受保护(甚至私有)...
编辑
在实践中,双重继承解决方案的效果并不好,因为在我的案例中它导致了菱形继承问题,这让事情变得非常复杂。受到@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;
};
这直接从根本上解决了问题,即方法被设置为受保护而不是 public,同时不会不必要地使继承情况复杂化。由于这只是通过成员函数指针删除受保护属性的技巧,因此应该是安全的:-)