C++:自定义 vtable 实现不起作用

C++ : custom vtable implementation does not work

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

#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 方法的 class

#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();
    }
}

不幸的是,我看到 'derived' 方法没有被调用。我是否使用了错误的方法来实现 vtable 概念?

Am I using a wrong approach to implement a vtable concept?

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

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

中查找

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

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

doc->PrintID();

不需要 vtable,也不检查。

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

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

#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;
}