【问题标题】:Overriding pure virtual functions覆盖纯虚函数
【发布时间】:2021-09-30 06:50:03
【问题描述】:

我一直试图弄清楚为什么当我尝试在 QTCreator 中构建此代码时出现错误。我发现了一些与此类似的其他帖子,但我认为我有一个类似但不同的问题。

在 main.cpp 中,我基本上有一些我正在创建的过滤器(具有纯虚函数的模板结构),它返回给我一个 MyProducts 的向量。我给过滤器一个规范(一个带有纯虚函数的模板结构)和一个 MyProduct 的向量来搜索。

但我在构建过程中不断收到此错误消息。

/Users/marcokok/Qt-workspace/Open_Closed_Principle/main.cpp:112: error: allocating an object of abstract class type 'Specification<MyProduct>'
/Users/marcokok/Qt-workspace/Open_Closed_Principle/main.cpp:112:36: error: allocating an object of abstract class type 'Specification<MyProduct>'
    AndSpecification<MyProduct> as(cs, ss);
                                   ^
/Users/marcokok/Qt-workspace/Open_Closed_Principle/main.cpp:29:18: note: unimplemented pure virtual method 'is_satisfied' in 'Specification'
    virtual bool is_satisfied(T* item) = 0;
                 ^

这是我的源代码

main.cpp

#include <iostream>
#include <vector>

using namespace std;

enum class ColorTable
{
    red,
    green,
    blue
};

enum class SizeTable
{
    small,
    medium,
    large
};

struct MyProduct
{
    string name;
    ColorTable color;
    SizeTable size;
};

template <typename T> struct Specification
{
    virtual bool is_satisfied(T* item) = 0;
};

template <typename T> struct Filter
{
    virtual vector<T*> filter(vector<T*> items, Specification<T>& spec) = 0;
};

struct BetterFilter : Filter<MyProduct>
{
    vector<MyProduct*> filter(vector<MyProduct *> items, Specification<MyProduct> &spec) override
    {
        vector<MyProduct*> result;

        for (auto& item : items)
        {
            if (spec.is_satisfied(item))
                result.push_back(item);
        }
        return result;
    }
};

struct ColorSpecification : Specification<MyProduct>
{
    ColorTable color;

    ColorSpecification(ColorTable color) : color(color)
    {}

    bool is_satisfied(MyProduct* item) override
    {
        if (item->color == color)
            return true;
        else
            return false;
    }
};

struct SizeSpecification : Specification<MyProduct>
{
    SizeTable size;

    SizeSpecification(SizeTable size) : size(size)
    {}

    bool is_satisfied(MyProduct* item) override
    {
        if (item->size == size)
            return true;
        else
            return false;
    }
};

template <typename T> struct AndSpecification : Specification<T>
{
    Specification<T>& first;
    Specification<T>& second;

    AndSpecification(Specification<T> first, Specification<T> second) : first(first) , second(second)
    {}

    bool is_satisfied(T* item) override
    {
        if (first.is_satisfied(item) && second.is_satisfied(item))
            return true;
        else
            return false;
    }

};
int main()
{
    MyProduct prod_1{"Apple", ColorTable::green, SizeTable::large};
    MyProduct prod_2{"Jeans", ColorTable::green, SizeTable::small};
    MyProduct prod_3{"Graphics Card", ColorTable::blue, SizeTable::large};

    vector<MyProduct*> items = {&prod_1, &prod_2, &prod_3};

    BetterFilter bf;
    ColorSpecification cs(ColorTable::green);
    SizeSpecification ss(SizeTable::large);
    AndSpecification<MyProduct> as(cs, ss);

    auto green_things = bf.filter(items, cs);
    for (auto& item : green_things)
        cout << item->name << " is green." << endl;

    auto large_things = bf.filter(items, ss);
    for (auto& item : large_things)
        cout << item->name << " is large.\n\n" << endl;

   // auto large_green_things = bf.filter(items, as);
    //for (auto& item : large_green_things)
     //   cout << item->name << " is large and green." << endl;
    return 0;
}

【问题讨论】:

    标签: c++ virtual-functions


    【解决方案1】:

    您的编译器会告诉您错误发生的位置。

    /Users/marcokok/Qt-workspace/Open_Closed_Principle/main.cpp:112:36: error: allocating an object of abstract class type 'Specification<MyProduct>'
       AndSpecification<MyProduct> as(cs, ss);
                                      ^
    

    当使用cs 作为AndSpecification&lt;MyProduct&gt; 构造函数的第一个参数时,会尝试实例化Specification&lt;MyProduct&gt;。创建cs 很好,但不知何故,这个函数调用触发了抽象类的构造。那么让我们看看那个构造函数的声明。

       AndSpecification(Specification<T> first, Specification<T> second) 
    

    第一个参数是按值传递的(与您的许多其他参数不同)。这意味着一个新构造的Specification&lt;MyProduct&gt;对象是从cs初始化的。 (查看csSpecification&lt;MyProduct&gt; 子对象,并将这些数据成员复制到新对象。)但是,Specification&lt;MyProduct&gt; 是抽象的(就像所有Specification&lt;T&gt; 类一样),因此无法构造它.因此出现错误。

    更改您的构造函数以通过引用接受参数。

            AndSpecification(Specification<T>& first, Specification<T>& second) 
            //                               ^                        ^
    

    看到了吗?如果您知道如何阅读错误消息,它并不像您说的那么复杂。

    【讨论】:

    • 所以当我构造AndSpecification&lt;MyProduct&gt; 时,我正在复制Specification&lt;T&gt; first。而且由于Specification &lt;T&gt; 有一个纯虚函数,我无法实例化它。但我已经为对象first 定义了纯虚函数。似乎复制的对象没有定义纯虚函数is_satisfied。但为什么呢?
    • @Marco 你没有为对象first定义了纯虚函数。该对象(复制的结果)的类型为Specification&lt;MyProduct&gt;。除非您编写代码来做一些不同的事情(甚至可能不会),否则按值传递只会复制 data 成员;成员函数被复制。嗯..也许“复制”不是这里最好的词选择。考虑“初始化”可能会更好。
    • 我现在完全理解这个问题了。谢谢@JaMiT
    【解决方案2】:

    我试图在 coliru 中重现 OPs 问题。
    这是我得到的:

    main.cpp: In function 'int main()':
    main.cpp:112:42: error: cannot allocate an object of abstract type 'Specification<MyProduct>'
      112 |     AndSpecification<MyProduct> as(cs, ss);
          |                                          ^
    main.cpp:27:30: note:   because the following virtual functions are pure within 'Specification<MyProduct>':
       27 | template <typename T> struct Specification
          |                              ^~~~~~~~~~~~~
    main.cpp:29:18: note:     'bool Specification<T>::is_satisfied(T*) [with T = MyProduct]'
       29 |     virtual bool is_satisfied(T* item) = 0;
          |                  ^~~~~~~~~~~~
    main.cpp:112:33: warning: unused variable 'as' [-Wunused-variable]
      112 |     AndSpecification<MyProduct> as(cs, ss);
          |                                 ^~
    

    我注意到的第一件事:struct AndSpecification 坏了:

    template <typename T> struct AndSpecification : Specification<T>
    {
        Specification<T>& first;
        Specification<T>& second;
    
        AndSpecification(Specification<T> first, Specification<T> second) : first(first) , second(second)
        {}
    };
    

    成员变量firstsecond 存储一个引用。但是,构造函数中的初始化是使用值参数完成的。 IE。成员使用局部变量进行初始化,这些变量在构造函数离开后被销毁(实际上,如果调用构造函数的完整表达式完成)。

    关于override,我没有头绪,所以我先解决了这个问题。

    ...得到(令我惊讶)以下输出:

    Apple is green.
    Jeans is green.
    Apple is large.
    
    
    Graphics Card is large.
    

    Live Demo on coliru

    我得出的结论是发现/修复的错误和错误消息是相关的但我仍然无法解释这一点。我手头最好的:如果代码包含U.B.,则无法对结果做出任何期望。

    @Ayxan Haqverdili的帮助下,我找到了错误的原因:
    值参数会导致调用中的临时副本。这些副本的类型为Specification&lt;T&gt;,但该类型是抽象的,无法实例化。

    【讨论】:

    • 问题是 OP 试图创建一个 Specification&lt;MyProduct&gt; 类型的对象,这是不可能的。就好像他做了Specification&lt;MyProductT&gt; copy = ColorSpecification{}。和UB没关系。
    • @AyxanHaqverdili 你的意思是,值参数导致对象切片?现在,事情开始对我有意义了。
    • 如果Specification 不是抽象的,就会导致切片。你不能实例化一个抽象类,所以它不能编译。
    猜你喜欢
    • 2013-10-04
    • 2020-08-21
    • 2022-01-13
    • 2014-05-22
    • 2014-03-08
    • 2011-02-24
    • 2013-01-16
    • 2021-11-01
    • 2017-06-28
    相关资源
    最近更新 更多