8

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

我可以访问 VS 2010 实现的 C++11:这意味着static_assert,enable_if并且<type_traits>可用。

4

3 回答 3

3

IMO,对于这个问题没有可用的干净且独立于平台的解决方案。

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

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

每当您想要进行此类健全性检查时,都必须定期遵循此过程(不幸的是)。

于 2013-05-09T16:49:43.430 回答
3

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

钻石

您可以明确地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,无论如何它有什么样的继承都无关紧要。

于 2015-07-31T15:27:57.847 回答
1

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

#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()可能可用于继承来重建名称,但我尝试的那些导致析构函数受到保护的编译器错误,所以我希望如果可能的话,它至少相对困难并且可能就足够了满足您的需求。

于 2013-05-09T16:32:52.073 回答