【问题标题】:Force deriving from a class virtually强制从一个类虚拟派生
【发布时间】:2015-10-23 04:30:13
【问题描述】:

我们的项目中有一个特殊的接口框架,部分要求是表示接口的类只能用作虚拟基类,不能用作非虚拟基类。有没有办法在代码中强制执行这一点?也就是说,如果类是从非虚拟派生的,则会产生编译错误。

我可以访问 VS 2010 实现的 C++11:这意味着 static_assertenable_if<type_traits> 可用。

【问题讨论】:

  • 现在可能有更好的方法来做到这一点,但在 VS2010 中有一个 Microsoft 特定的关键字,称为 __interface。但是,这是 a) 不可移植 b) 您必须小心,因为出于某种原因,这些接口不允许有虚拟析构函数。
  • 这听起来更像是技术规范而不是功能要求。为什么需要这个?
  • 是否需要在编译时强制执行?您可以write a little tool using libclang 进行检查,并将其用作代码审查/构建过程的一部分。
  • 如果一个类真的只是一个接口,即它没有方法实现或成员变量,那么它是否被虚拟继承并不重要。
  • @Clark 不正确。如果它是非虚拟继承的,则层次结构中可能有该类的两个实例,具有两个实现,并且转换为该接口可能导致对同一对象使用不同的方法。这可能是高度混乱和意外的行为。如果到处都是virtual,那么任何实现方法的类都会在整个层次结构中实现它。

标签: c++ c++11 typetraits virtual-inheritance static-assert


【解决方案1】:

这可以在编译时检查。关键是,如果我们有菱形图案:

您可以明确地将D& 转换为A&。但是,如果继承是非虚拟的:

演员表会模棱两可。所以让我们试着制作一颗钻石吧!

template <typename Base, typename Derived>
class make_diamond {
    struct D2 : virtual Base { }; // this one MUST be virtual
                                  // otherwise we'd NEVER have a diamond
public:
    struct type : Derived, D2 { };
};

此时它只是另一个void_t-style 类型特征:

template <typename Base, typename Derived, typename = void> 
struct is_virtual_base_of : std::false_type { };

template <typename Base, typename Derived>
struct is_virtual_base_of<Base, Derived, void_t<
    decltype(static_cast<Base&>(
        std::declval<typename make_diamond<Base, Derived>::type&>()))
    >> : std::true_type { };

如果转换是明确的,则部分特化中的表达式将是有效的,并且将首选该特化。如果演员表模棱两可,我们将有一个替换失败,并最终得到主要的。注意这里的Base 实际上不需要有任何virtual 成员函数:

struct A { };
struct B : public A { };
struct C : virtual  A { };

std::cout << is_virtual_base_of<A, B>::value << std::endl; // 0
std::cout << is_virtual_base_of<A, C>::value << std::endl; // 1

如果它有任何纯虚成员函数,我们不必重写它们,因为我们从未真正构造过对象。

struct A2 { virtual void foo() = 0; };
struct B2 : public A2 { void foo() override { } };
struct C2 : virtual A2 { void foo() override { } };

std::cout << is_virtual_base_of<A2, B2>::value << std::endl; // 0
std::cout << is_virtual_base_of<A2, C2>::value << std::endl; // 1

当然,如果你的班级被标记为final,这根本行不通。但是,如果它是final,无论如何它有什么样的继承都无关紧要。

【讨论】:

  • 我知道检查基类是否为虚拟的更简单的方法(例如强制转换、指针到成员的转换等),但这些方法会受到基类可访问性的影响。您使用歧义检查的方法不受访问保护的影响,因此 +1。但是,它受到已经存在的歧义的影响,例如struct D : B, C {}; 所以is_virtual_base_of 类似于is_nonfinal_unambiguous_virtual_base_of ;)
  • @dyp 不确定您所说的现有歧义是什么意思?
  • D d; A&amp; a = d; 不明确:coliru.stacked-crooked.com/a/d92de17312866f0b
  • @dyp 0, 1, 0 似乎是对的,尽管有烦人的 gcc 警告。
  • 嗯,AD 的虚拟基础,所以我希望is_virtual_base_of&lt;A, D&gt;::value 是真的。另一方面,当前的行为可能对 OP 更有用。
【解决方案2】:

IMO,没有针对此问题的干净且独立于平台的解决方案。

最好的方法是手动将每个继承更改为virtual继承。
要做到这一点,识别接口的派生类(比如class Base)很容易(!)。可以按照以下步骤进行:

  1. class Base 设为final (c++11);即class Base final { ...
  2. 编译代码,它的所有代码都会产生编译错误 派生类
  3. 去检查每个派生类并将继承设置为 virtual
  4. 去掉final关键字,代码编译成功

这个过程(很遗憾)必须定期执行,只要您想进行这种健全性检查。

【讨论】:

    【解决方案3】:

    有趣的问题。您可以通过隐藏接口类并公开从虚拟接口继承的具体类来接近您想要的。这显然需要一些解决方法和尴尬,但它可能会适应您的需求。这是一个例子:

    #include <iostream>
    using namespace std;
    
    class Hide {
        struct VInterface {
            void foo() const { cout << "VInterface::foo()\n"; }
            VInterface const &as_interface() const { return *this; }
        protected:
            virtual ~VInterface() { }
        };
    public:
        struct VBase : virtual VInterface {
        };
    };
    typedef Hide::VBase VBase;
    struct VDiamond1 : VBase { };
    struct VDiamond2 : VBase { };
    struct VConcrete : VDiamond1, VDiamond2 { };
    
    int main() {
        VConcrete vc;
        auto const &vi = vc.as_interface();
        vi.foo();
    }
    

    可能可以使用decltype()as_interface() 重构一个名称,该名称可能可用于继承,但我尝试的那些导致析构函数受到保护的编译器错误,所以我希望如果可能的话,这至少相对困难,可能足以满足您的需求。

    【讨论】:

      猜你喜欢
      • 2021-06-14
      • 2013-10-07
      • 1970-01-01
      • 1970-01-01
      • 2019-11-04
      • 2013-09-28
      • 2011-01-15
      相关资源
      最近更新 更多