【问题标题】: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,它使用BCf(),具体取决于运行时可用的信息。我无法修改库,并且从B 继承C 是不切实际的,因为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 不相关。试图将一个视为其他之一是未定义的行为。

标签: c++ protected


【解决方案1】:

不,你所拥有的并不安全。 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;
};

这个想法是最多保持一个指针非空(或者找到另一种方法来确定您是否有 BBCboost::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 的回答相反,如果有很多类,如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() 公开,整个问题就会消失...所以,图书馆设计师,请公开您的方法!您无法预测所有用例,您只是在强迫我们通过或多或少粗略的方法绕过您的受保护(甚至是私人)......

      编辑

      在实践中,双重继承解决方案效果不佳,因为它在我的案例中导致了钻石继承问题,这使事情变得非常复杂。受到@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;
      };
      

      这直接从根本上解决了问题,即方法被设置为受保护而不是公共的,同时不会不必要地使继承情况复杂化。由于这只是通过成员函数指针删除受保护属性的技巧,因此它应该是安全的:-)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-03-09
        • 2016-10-30
        • 1970-01-01
        • 1970-01-01
        • 2014-04-08
        • 2012-09-21
        • 2011-03-20
        • 1970-01-01
        相关资源
        最近更新 更多