纯虚拟成员有什么好处吗(除了他们可能防止的人为错误)?

Are there any advantages to pure virtual members (except the human error that they might prevent)?

提问人:Eris 提问时间:7/25/2022 最后编辑:Vlad from MoscowEris 更新时间:7/25/2022 访问量:90

问:

我有一堆带有纯虚拟成员的类,它将由派生的非抽象类填充。我收到错误:

Error   C2259   'ABC': cannot instantiate abstract class    TEMP    c:\program files (x86)\microsoft visual studio\2017\enterprise\vc\tools\msvc\14.16.27023\include\xmemory0   879 

这个错误可以通过使用 来解决,但是堆内存访问比堆栈内存慢,所以我正在考虑重写根本没有纯虚拟成员的基类 ABC。stack<ABC*>

这有什么缺点吗(除了可能使用此代码的人可能的人为错误)?

有没有办法在堆栈上创建具有纯虚拟成员的类堆栈?或者,也许,我对使用堆太偏执了?该类(在原始代码中)将被非常频繁地访问。

代码的简化版本如下:

#include <iostream>
#include <stack>

class ABC {
public:
    ABC(int& a) : m_a(a) {}
    ~ABC() {}
    virtual void do_something() = 0;
    int m_a;
};

class DEF : public ABC {
public:
    DEF(int& a) : ABC(a) {}
    void do_something() override;
    ~DEF() {}
};

void DEF::do_something() {
    std::cout << "Hi!\n";
}

int main(int argc, char* argv[]) {
    int x = 123;
    std::stack<ABC> s;

    s.push(DEF(x));
}
C++ 多态性 纯-虚拟

评论

2赞 463035818_is_not_an_ai 7/25/2022
您将虚拟调度与堆栈/堆混淆。纯虚函数不需要堆分配。但无论如何,它确实将其元素存储在堆上,因此您担心的事情是错误的std::stack
1赞 Stephen Newell 7/25/2022
即使没有纯 s,您当前的方法也会导致对象切片。查看 stackoverflow.com/questions/274626/what-is-object-slicingvirtual
0赞 WhozCraig 7/25/2022
仅供参考,这段代码像剃须刀一样切片
0赞 PaulMcKenzie 7/25/2022
std::stack<ABC> s;-- 这在内部使用 ,它使用堆。std::deque
0赞 463035818_is_not_an_ai 7/25/2022
虚函数通常被说成是有开销的,尽管这种开销只是与没有运行时多态性相比。一旦你选择了运行时多态性,(纯)虚函数就没有错了。

答:

1赞 Vlad from Moscow 7/25/2022 #1

在此通话中

s.push(DEF(x));

该类型的临时对象将隐式转换为 类型的对象。因此,如果要调用虚函数,则将调用类 ABC 的虚函数。DEFABC

在使用指针或引用时,可以使用多态性。

这是您更新的程序。

#include <iostream>
#include <stack>

class ABC {
public:
    ABC(int& a) : m_a(a) {}
    ~ABC() {}
    virtual void do_something() 
    {
        std::cout << "Bye!\n";
    };
    int m_a;
};

class DEF : public ABC {
public:
    DEF(int& a) : ABC(a) {}
    void do_something() override;
    ~DEF() {}
};

void DEF::do_something() {
    std::cout << "Hi!\n";
}

int main()
{
    int x = 123;
    std::stack<ABC> s;

    s.push(DEF(x));

    s.top().do_something();
}

程序输出为

Bye!

正如你所看到的,有对象切片。

请注意,您需要使析构函数成为虚拟的。你可以只在基类中编写

virtual ~ABC() = default;

评论

0赞 Unmitigated 7/25/2022
应该注意的是,析构函数也应该是虚拟的。
1赞 Vlad from Moscow 7/25/2022
@Unmitigated 一句好话。我已经附上了答案。
0赞 SoronelHaetir 7/25/2022 #2

您不能拥有具有虚拟成员(纯成员或非虚拟成员)的对象容器(堆栈就是一个示例)并保持动态行为。您可以有一个指向此类对象的指针容器并维护动态行为,但不能保留此类对象本身。

使用纯虚拟的时候是基类根本没有合理的实现可用的时候。想想像流缓冲区这样的标准类型,基数只是定义一个接口,但基数实际上对许多操作没有任何作用。只有派生类型知道它应该做什么。