抽象类作为接口,没有vtable

时间:2016-10-26 15:10:23

标签: c++ c++11 inheritance interface abstract-class

我想创建一个定义类的某些方法的抽象类。其中一些应该由基类(Base)实现,一些应该在Base中定义但是由Derived覆盖,而其他应该在Base中是纯虚拟的,以强制在Derived中定义。

这当然是抽象类的用途。但是,我的应用程序只会直接使用Derived对象。因此,编译器应该在编译时确切地知道要使用哪些方法。

现在,由于此代码将在内存非常有限的微控制器上运行,因此我非常希望避免实际使用带有vtable的虚拟类。根据我的测试,似乎编译器足够聪明,除非必须,否则不能制作vtable,至少在某些情况下。但是我被告知永远不要相信编译器:是否有可能使这成为编译的必要条件?

以下是一些代码示例:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">

    <title>Registration Form</title>
    <link href="style/bootstrap-3.3.7.min.css" rel="stylesheet" />
    <script src="script/jquery-1.12.4.min.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css " />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <script type="text/javascript ">
        var limit = 10;
        var Name;
        var FatherName;
        var CellNo;

        $(document).ready(function () {
            $("#btnSubmit").click(function () {
                $('#txtName').val();
                if (name == "") {
                    alert("please enter the name");
                    return  ;
                }
            });

                $('#txtFatherName').val();
                if (FatherName == "") {
                    alert("please enter ur father name");
                    return;
                }
                $('#txtEmail').val();
                if (Email == "") {
                    alert("please enter email");
                    return;
                }
                $('#txtAddress').val();
                if (Address == "") {
                    alert("please enter email");
                    return;
                }
                $('#txtCellNo').val();
                if (CellNo == "") {
                    alert("please enter email");
                    return;
                }

        });

</script>


</head>
<body>
    <form id="form1" runat="server">
        <div id="personalInfo">
            <div>
                <div class="container">
                    <h1>Personal Information</h1>

                </div>
                <br />
                <div class="row">
                    <div class="col-sm-2 text-right">Name :</div>
                    <div class="col-sm-4">
                        <input type="text" id="txtName" class="cls1" name="txtName" size="20" />
                    </div>
                </div>
                <br />
                <div class="row">
                    <div class="col-sm-2 text-right">Father Name :</div>
                    <div class="col-sm-4">
                        <input type="text" id="txtFatherName" class="cls1" name="txtFatherName" size="20" />
                    </div>
                </div>
                <br />

            </div>
            <div class=" row">
                <div class="col-sm-2 text-right">Email:</div>
                <div class="col-sm-4">
                    <input type="text" id="txtEmail " class="cls1" name="txtEmail" size="20" />
                </div>
            </div>

            <div>
                <br />
                <div class="row">
                    <div class="col-sm-2 text-right">Address</div>
                    <div class="col-sm-4">
                        <input type="text" id="txtAddress" class="cls1" name="txtAddress" size="20" />
                    </div>
                </div>
                <br />
                <div class="row">
                    <div class="col-sm-2 text-right">Cell No:</div>
                    <div class="col-sm-4">
                        <input type="text" id="txtCellNo" class="cls1" name="txtCellNo" size="20" />
                    </div>
                </div>
                <br />

                <div class=" row">
                    <div class=" col-sm-2 text-right"></div>
                    <div class="col-sm-2">
                        <button type="button" id="btnSubmit" class="btn btn-primary">Submit</button>
                        <button type="button" id="btnCancel" class="btn btn-default">Cancel</button>
                    </div>

                </div>
            </div>
        </div>

    </form>
</body>
</html>

没有vtable

这没有vtable,是我想要的

class Base {
  public:
    Base() {}
    virtual ~Base() {};

    virtual int thisMustBeDefined() = 0;
    virtual int thisCouldBeOverwritten() { return 10; }
    int thisWillBeUsedAsIs() { return 999; }
};

class Derived : public Base {
  public:
    Derived() {}
    ~Derived() {}

    int thisMustBeDefined() { return 11; }

};

是vtable 1

由于我的草率编码,我错误地强迫编译器使用多态,因此需要一个vtable。如何让这个案例抛出错误?

int main() {
  Derived d;
  d.thisMustBeDefined();
}

是vtable 2

这里我没有提到班级&#34; Base&#34;在任何时候,编译器都应该知道所有方法都是在编译时预先确定的。然而,它仍然创造了一个vtable。这是我希望能够通过编译错误检测到这一点的另一个例子。

int main() {
  Base * d;
  d = new Derived();
  d->thisMustBeDefined();
}

换句话说,如果我编写导致编译器为我的类生成vtable的代码,即使用多态,我希望它是编译器错误。

1 个答案:

答案 0 :(得分:6)

正如评论中已经提到的那样,你可以使用CRTP(又名静态多态)来避免创建vtable:

template <typename Der>
class Base {
  public:
    Base() {}
    ~Base() {};

    int thisMustBeDefined() {
        // Will fail to compile if not declared in Der
        static_cast<Der*>(this)->thisMustBeDefined();
    }
    int thisCouldBeOverwritten() { return 10; }
    int thisWillBeUsedAsIs() { return 999; }
};

class Derived : public Base<Derived> {
  public:
    Derived() {}
    ~Derived() {}

    int thisMustBeDefined() { return 11; }

    // Works since you call Derived directly from main()
    int thisCouldBeOverwritten() { return 20; }

};

如果Derived中没有实现某个函数,可以使编译器错误更具可读性,您可以使用this answer中提供的简单静态检查:

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)               \
    template <typename U>                                                   \
    class traitsName                                                        \
    {                                                                       \
    private:                                                                \
        template<typename T, T> struct helper;                              \
        template<typename T>                                                \
        static std::uint8_t check(helper<signature, &funcName>*);           \
        template<typename T> static std::uint16_t check(...);               \
    public:                                                                 \
        static                                                              \
        constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \
    }

DEFINE_HAS_SIGNATURE(thisMustBeDefined, T::thisMustBeDefined, int(*)(void));

并将静态检查添加到Base构造函数:

Base() {
    static_assert(thisMustBeDefined<Der>::thisMustBeDefined, 
                  "Derived class must implement thisMustBeDefined");
}

虽然在使用小型设备时应考虑一个缺点,并且您一次拥有更多版本的Derived,但每个Base的{​​{1}}代码都会重复实例

因此,您必须确定对您的用例更重要的限制是什么。

正如@ChrisDrew在comment中指出的那样,将DerivedthisCouldBeOverwritten()函数移动到thisWillBeUsedAsIs()模板类派生的另一个基类会促进该问题。