【问题标题】:C++ : custom vtable implementation does not workC++:自定义 vtable 实现不起作用
【发布时间】:2021-06-15 20:51:55
【问题描述】:

我正在尝试实现自定义 vtable 以更好地理解虚拟表和覆盖的概念。为此,我有以下“基”类

#pragma once

#include <iostream>
#include <string>

using namespace std::string_view_literals;

struct vtable;

class IdentityDocument {
public:
    IdentityDocument()
        : vtable_ptr_(&IdentityDocument::VTABLE),
          unique_id_(++unique_id_count_)
    {
        std::cout << "IdentityDocument::Ctor() : "sv << unique_id_ << std::endl;
    }

    ~IdentityDocument() {
        --unique_id_count_;
        std::cout << "IdentityDocument::Dtor() : "sv << unique_id_ << std::endl;
    }

    IdentityDocument(const IdentityDocument& other)
        : vtable_ptr_(other.vtable_ptr_),
          unique_id_(++unique_id_count_)
    {
        std::cout << "IdentityDocument::CCtor() : "sv << unique_id_ << std::endl;
    }

    IdentityDocument& operator=(const IdentityDocument&) = delete;

    void PrintID() const {
        std::cout << "IdentityDocument::PrintID() : "sv << unique_id_ << std::endl;
    }

    static void PrintUniqueIDCount() {
        std::cout << "unique_id_count_ : "sv << unique_id_count_ << std::endl;
    }

    int GetID() const {
        return unique_id_;
    }

private:
    vtable* vtable_ptr_ = nullptr;

    static int unique_id_count_;
    static vtable VTABLE;
    int unique_id_;
};

struct vtable
{
    void (IdentityDocument::* const PrintID)() const;

    vtable (
        void (IdentityDocument::* const PrintID)() const
    ) : PrintID(PrintID) {}
};

int IdentityDocument::unique_id_count_ = 0;
vtable IdentityDocument::VTABLE = {&IdentityDocument::PrintID};

这是另一个必须覆盖 PrintId 方法的类

#pragma once

#include "identity_document.h"
#include <iostream>
#include <string>
#include <ctime>

using namespace std::string_view_literals;

class Passport {
public:
    Passport()
        : expiration_date_(GetExpirationDate())
    {
        IdentityDocument* base_ptr = reinterpret_cast<IdentityDocument*>(this);
        vtable* vtable_ptr = reinterpret_cast<vtable*>(base_ptr);
        vtable_ptr = &Passport::VTABLE;

        std::cout << "Passport::Ctor()"sv << std::endl;
    }

    Passport(const Passport& other)
        : identity_(other.identity_)
        , expiration_date_(other.expiration_date_)
    {
        IdentityDocument* base_ptr = reinterpret_cast<IdentityDocument*>(this);
        vtable* vtable_ptr = reinterpret_cast<vtable*>(base_ptr);
        vtable_ptr = &Passport::VTABLE;

        std::cout << "Passport::CCtor()"sv << std::endl;
    }

    ~Passport() {
        std::cout << "Passport::Dtor()"sv << std::endl;
    }

    void PrintID() const {
        std::cout << "Passport::PrintID() : "sv << identity_.GetID();
        std::cout << " expiration date : "sv << expiration_date_.tm_mday << "/"sv << expiration_date_.tm_mon << "/"sv
                  << expiration_date_.tm_year + 1900 << std::endl;
    }

    void PrintVisa(const std::string& country) const {
        std::cout << "Passport::PrintVisa("sv << country << ") : "sv << identity_.GetID() << std::endl;
    }

private:
    IdentityDocument identity_;
    const struct tm expiration_date_;
    static vtable VTABLE;

    tm GetExpirationDate() {
        time_t t = time(nullptr);
        tm exp_date = *localtime(&t);
        exp_date.tm_year += 10;
        mktime(&exp_date);
        return exp_date;
    }
};

vtable Passport::VTABLE = {reinterpret_cast<void (IdentityDocument::*)() const>(&Passport::PrintID)};

还有一个简短的演示:

int main() {
    array<IdentityDocument*, 1> docs = { (IdentityDocument*)(new Passport()) };
    for (const auto* doc : docs) {
        doc->PrintID();
    }
}

不幸的是,我看到没有调用“派生”方法。我是否使用了错误的方法来实现 vtable 概念?

【问题讨论】:

  • 您在使用调试器单步执行代码时观察到了什么?
  • 您希望动态绑定发生在哪里?
  • 动态绑定在 C++ 中使用 virtual 关键字完成。将变量命名为“vtable”不会使 C++ 神奇地将其视为 vtable。如果您想模拟虚拟调度(这是一个很好的中级 C++ 练习),您必须自己全部完成。编译器不会带你到一半。就 C++ 编译器而言,上面示例中的所有类都没有 vtable,改变它的唯一方法是声明 virtual 函数/超类,此时您不再编写自定义实现
  • 你一直在说“vtable 设置为”。那是不是一件事。 C++ 不允许你“设置”vtables。曾经。这不是语言的工作方式。如果您尝试使用指针并“欺骗”C++ 认为在没有放置 vtable 的地方存在 vtable,那么这是未定义的行为。
  • @Dmitry 只有当您使用virtual 时,您才会获得动态绑定。如果您想自己执行此操作,那么您需要做的基本上就是放入编译器将为您执行的代码。基类将有一个函数指针表,派生类将使用正确的函数指针填充该表。然后基类函数只需要检查那个 vtable 变量。如果该条目为空,则它知道使用基类函数。如果不为null,则调用vtable指向的函数。

标签: c++ vtable


【解决方案1】:

我是否使用了错误的方法来实现 vtable 概念?

是的。您还没有编写任何读取 vtable 的代码,C++ 编译器也不会生成任何代码来读取您的 vtable。

当您声明成员函数virtual 时,您的编译器需要以特殊方式调用该函数。对该函数的任何调用都应在 vtable 中查找

当成员函数不是virtual时,您的编译器知道它不需要查找函数的位置。它知道要调用哪个函数。无需查找。

在您的代码中,您创建了一个 vtable,但是这一行调用了一个非虚拟函数:

doc->PrintID();

不需要vtable,也不检查vtable。

doc 是一个IdentityDocument*,所以doc-&gt;PrintID() 调用IdentityDocument::PrintID()。无需查找,不会发生查找。

【讨论】:

  • 编译器不知道表但是指针算术呢? Passport 的第一个成员是 IdentityDocument,所以我可以通过 reinterpret_cast 访问它,对吧?因此,使用这个指针我可以访问它的 vtable 成员,该成员又在 Password ctor 中设置。所以应该没问题但是还是不行……
  • @Dmitry 当你写 doc-&gt;PrintID(); 时,因为 PrintID 没有标记为 virtual,所以对 PrintID 的调用被调度到 @ 的 static 类型987654332@ 即IdentityDocument。为什么编译器还要关心Passport 的第一个成员是什么?
  • @Zoso 因为 doc 是指向 Passport 的指针,确切地说是它的第一个成员,即 IdentityDocument,vtable 结构设置为 Passport::VTABLES。我错过了什么吗?
  • @Dmitry 是的,你错过了你写的array&lt;IdentityDocument*, 1&gt;doc 的类型是 IdentityDocument* not Passport*。当你写(IdentityDocument*)(new Passport()) 时,你明确地做了一个cast。没有virtual 关键字,没有多态酱。
  • @DrewDormann 感谢您指出我的误解 - 我修改了我的解决方案并得到了我想要的!!!
【解决方案2】:

最后我简化了我的解决方案并得到了我想要的:

#include <iostream>
class A;
struct VTable
{
    void (*say_hello)(A*);
};
class A
{
public:
    A()
    {
        vtable.say_hello = A::sayHello;
    }

    void sayHello()
    {
        vtable.say_hello(this);
    }
    static void sayHello(A* a)
    {
        std::cout << "A::sayHello" << std::endl;
    }
    VTable vtable;
};
class B
{
public:
    B()
    {
        a.vtable.say_hello = B::sayHello;
    }
    void sayHello()
    {
        a.vtable.say_hello((A*)this);
    }
    static void sayHello(A* a)
    {
        std::cout << "B::sayHello\n" << std::endl;
    }
private:
    A a;
};

int main()
{
    A* a = (A*)(new B);
    a->sayHello();
    delete a;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-23
    • 2022-01-25
    • 2019-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-13
    相关资源
    最近更新 更多