【发布时间】: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指向的函数。