Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding pure virtual functions

I've been trying to figure out why I get an error when I try to build this code in QTCreator. I found some other posts similar to this but I think I have a similar but different issue.

In main.cpp I basically have some Filter (a templated struct with a pure virtual function) that I'm creating that returns to me a vector of MyProducts. I give the filter a Specification (a templated struct with a pure virtual function) and a vector of MyProduct to search through.

But I keep getting this error message during the build.

/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;
                 ^

Here is my source code

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;
}
like image 556
Marco Avatar asked Dec 31 '22 13:12

Marco


2 Answers

I tried to reproduce the OPs issue in coliru.
This is what I got:

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);
      |                                 ^~

The first thing I noticed: The struct AndSpecification is broken:

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

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

The member-variables first and second store a reference. However, the initialization in the constructor is done with value arguments. I.e. the members are initialized with local variables which are destroyed after the constructor is left (actually if the full-expression calling the constructor is done).

Concerning the override, I hadn't a clue, so I fixed this first.

…and got (to my surprise) the following output:

Apple is green.
Jeans is green.
Apple is large.


Graphics Card is large.

Live Demo on coliru

I came to the conclusion that the found/fixed bug and the error message were related but I still fail to explain this. The best I've at hand: If the code contains U.B. no expectations can be made about the outcome.

With the help of @Ayxan Haqverdili, I found out why the error is justified:
Value parameters cause temporary copies in the call. These copies would be of type Specification<T> but that type is abstract and cannot be instanced.

like image 157
Scheff's Cat Avatar answered Jan 02 '23 04:01

Scheff's Cat


Your compiler tells you where the error occurs.

/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);
                                  ^

The attempt to instantiate Specification<MyProduct> occurs when using cs as the first argument to the AndSpecification<MyProduct> constructor. Creating cs was fine, but somehow this function call triggers the construction of an abstract class. So let's look at the declaration of that constructor.

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

The first parameter is passed by value (unlike many of your other parameters). This means that a newly-constructed Specification<MyProduct> object is initialized from cs. (The Specification<MyProduct> sub-object of cs is looked at, and those data members are copied to the new object.) However, Specification<MyProduct> is abstract (as are all Specification<T> classes), so it cannot be constructed. Hence the error.

Change your constructor to accept arguments by reference.

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

See? It's not as complicated as you made it appear, if you know how to read the error message.

like image 44
JaMiT Avatar answered Jan 02 '23 02:01

JaMiT