C 我应该使用虚拟方法

C++ should I use virtual methods?

本文关键字:虚拟 方法 我应该      更新时间:2023-10-16

让我开始告诉我我了解虚拟方法的工作方式(多态性,后期可结合,vtables)。

我的问题是我是否应该使我的方法虚拟。我将在特定案例上体现我的困境,但也会欢迎任何一般准则。

上下文:

我正在创建一个库。在此库中,我有一个CallStack类,该类捕获了呼叫堆栈,然后提供类似向量的访问捕获的堆栈帧。捕获是通过protected方法CaptureStack完成的。如果库的用户希望实现捕获堆栈的另一种方法,则可以在派生类中重新定义此方法。需要明确的是,使该方法virtual的讨论仅适用于我知道可以在派生类中重新定义的某些方法(在本例中为CaptureStackdestructor),不适用于所有类方法。

在我的库中,我都使用CallStack对象,但从未作为指针或参考参数暴露,因此仅考虑使用我的库。

不需要virtual

我想不出有人想将CallStack用作指针或引用以实现多态性的情况。如果某人想衍生CallStack并重新定义CaptureStack,我认为仅使用派生的类对象就足够了。

现在仅仅是因为我不认为需要多态性,如果我不使用 virtual方法,或者我应该使用 virtual,而不仅仅是因为可以重新定义方法。


示例如何在我的库外面使用CallStack

if (error) {
  CallStack call_stack; // the constructor calls CaptureStack
  for (const auto &stack_frame : call_stack) {
    cout << stack_frame << endl;
  }
}

一个派生的类,重新定义CaptureStack可以以相同的方式使用,而不需要多态性:

if (error) {
  // since this is not a CallStack pointer / reference, virtual would not be needed.
  DerivedCallStack d_call_stack; 
  for (const auto &stack_frame : d_call_stack) {
    cout << stack_frame << endl;
  }
}

如果您的库在构造函数期间保存了调用堆栈,则您不能使用虚拟方法。

这是C 。从另一种语言来到C 时,人们通常会出错的一件事是在构造函数中使用虚拟方法。这从未按计划进行。

c 在每个构造函数调用期间设置虚拟函数表。这意味着从构造函数调用时,功能是从不虚拟的。虚拟方法始终指出当前类正在构造。

因此,即使您确实使用虚拟方法捕获堆栈,构造函数代码始终将调用基类方法。

要使它起作用,您需要从构造函数中取出电话并使用以下内容:

CallStack *stack = new DerivedStack;
stack.CaptureStack();

您的代码示例均未显示出使CaptureStack Virtual的充分理由。

在确定是否需要virtual功能时,您需要查看派生和覆盖功能是否会更改您现在正在实现的其他功能的预期行为/功能。

如果您依靠同一类的其他过程中的该特定函数的实现,例如同一类的另一个函数,那么您可能希望将函数作为虚拟。但是,如果您知道该函数应该在您的父班中执行什么,并且就您而言,您不希望任何人更改它,那么它不是virtual函数。

或另一个例子,想象一下某人从您实现中派生一个类,覆盖函数,并将将对象传递给父类,转换为您自己的实现函数/类之一。您是否希望对该功能进行原始实现,或者希望它们使用自己的Overriden实现?如果是后者,那么您应该选择virtual,除非不是。

我不清楚在哪里调用CallStack。从您的示例,看起来您正在使用模板方法模式,其中基本功能在基类,但通过虚拟函数自定义(通常是私人,不受保护),由派生的类。在这种情况下(正如彼得·布卢姆菲尔德指出的那样),这些功能必须是虚拟的,因为它们将从在基类的成员功能中;因此,有一个静态CallStack的类型。但是:如果我理解你的例子正确地,将在构造函数中呼叫CallStack。这将行不通,例如在CallStack的构建过程中该对象的动态类型是CallStack,而不是 DerivedCallStack,虚拟功能调用将转变为 CallStack

在这种情况下,对于您描述的用例模板可能更合适。甚至...课很明确。我想不出任何合理的情况不同的实例应具有捕获的不同手段在单个程序中调用stack 。这表明链接时间该类型的分辨率可能是适当的。(我用汇编防火墙成语和链接时间分辨率自己 StackTrace类。)

我的问题是我是否应该使我的方法虚拟。我将在特定案例上体现我的困境,但也会欢迎任何一般准则。

一些准则:

  • 如果您不确定,则不应该这样做。很多人会告诉您,您的代码应该很容易扩展(因此是虚拟的),但是实际上,除非您制作将大量使用的库(请参阅Yagni Arlioniple),否则最可扩展的代码永远不会扩展。

  • 在许多情况下而且它们根本不是可以继承的)。
  • 如果(当您设计代码/公共接口时),您意识到自己有一个以上的类,"是"是"另一个类"界面的实现,则应使用虚拟函数。

    <</p>

您几乎应该将方法声明为 virtual

第一个原因是基类中调用CaptureStack的任何内容都将通过基类指针(即本地this指针)这样做。因此,即使派生的类掩盖了它,它也会调用该函数的基类版本。

考虑以下示例:

class Parent
{
public:
    void callFoo()
    {
        foo();
    }
    void foo()
    {
        std::cout << "Parent::foo()" << std::endl;
    }
};
class Child : public Parent
{
public:
    void foo()
    {
        std::cout << "Child::foo()" << std::endl;
    }
};
int main()
{
    Child obj;
    obj.callFoo();
    return 0;
}

使用类的客户端代码仅使用派生对象(而不是基类指针等)。但是,实际上是foo()的基类版本。解决foo()虚拟的唯一方法。

第二个原因只是正确设计之一。如果派生的类函数的目的是覆盖而不是 bask 原件,那么除非有特定的原因(例如绩效问题),否则它应该这样做。如果您不这样做,那么您将来会诱使错误和错误,因为该类可能无法按照预期的方式行事。