这种用虚拟保护方法扩展库的方法安全吗?

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,它根据运行时可用的信息使用 BCf()。我无法修改库,并且 CB 继承是不切实际的,因为 BC 复杂得多。这就是我想出的:

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++ 上都能正常工作。但它通常安全吗?如果没有,有更好的方法吗?

不,你拥有的并不安全。 BC 不继承自 _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;
};

这个想法是让最多一个指针不为空(或者找到另一种方法来确定你是否有 BBC - 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 的回答相反,如果有许多 类,如 BC,它可以很好地扩展。我不得不稍微修改它以适用于我的示例:

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,同时不会不必要地使继承情况复杂化。由于这只是通过成员函数指针删除受保护属性的技巧,因此应该是安全的:-)