Pure virtual function issue
Asked Answered
H

2

6

I have a base class in which I have a pure virtual function and with this function, I want to override it in other derived classes (in some of those with a different number of parameters if possible).

So in the MergeSort subclass, I have MSort method which will need a different number of parameters as it is done recursively.

So having this function at the moment with those parameters I'm getting this error 'MergeSort:' cannot instantiate abstract class. BUT if I override the Sort method from the base class works fine, but I don't need one parameter.

I also tried to declare another virtual function with a different number of parameters and define it in MergeSort class and I get the same thing.

I'd also like to clarify that I have other subclasses for different algorithms (bubble sort, insertion sort etc) which are implemented similarly to MergeSort (a constructor and a sort function) but the sort function has the same no of parameters(just one used for a graphical interface) like in the base class from above.

So is it possible to have an overridden method with a different number of parameters? Or any other solution to what I've said above?

// BASE CLASS
// Forward declaration
class Interface;

/**
 * Base class from which the sorting algorithms classes will inherit (Polymorphic class) 
 * The base class will allow us to create a sequence with n elements    
 */
class SortingAlgorithms
{

protected:
    std::vector<sf::RectangleShape> sequence;       // vector which will contain a randomized sequence
    std::vector<sf::RectangleShape> sequenceCpy;    // a copy of sequence used for interaction features
    sf::RenderWindow& window;                       // initializes the window
    int minimum, maximum;                           // the range in which the elements will be randomized
    int elements;                                   // the number of elements which will be initialized

public:
    SortingAlgorithms();

    /** SortingAlgorithms() - class constructor which initializes the sequence
     *  @param min - the minimum value for randomizing
     *  @param max - the maximum value for randomizing
     *  @param els - the number of elements to generate
     *  @param win - since the window will be initialized only once (singleton pattern); 
     *               it will be needed to pass on this object to almost every function that has 
                     graphics features
     */
     SortingAlgorithms(int min, int max, int els, sf::RenderWindow& win);


     // A pure virtual function for overriding and param init which is what I described about win param from SortingAlgorithms constructor 
     virtual void Sort(std::unique_ptr<Interface>& init) = 0;
};



class MergeSort : public SortingAlgorithms
{
public:
    MergeSort(int min, int max, int els, sf::RenderWindow& win);
    
    void Merge(std::unique_ptr<Interface>& init, int first, int mid, int last);
    void MSort(std::unique_ptr<Interface>& init, int first, int last);
};
Harmonyharmotome answered 24/8, 2020 at 17:25 Comment(4)
You have to use the same signature on all of your overrides.Creator
If the derived classes need different parameters than what the abstract class expects, that's a bad code smell. It indicates that the derived class cannot fulfill the requirements of the base class, when means the polymorphic is-a relationship is a bit sketchy. Or that the base class is too constraining, and perhaps needs to be reworked so it's a better fit for the derived classes use cases.Gause
I think you should override Sort function without trying to change the signature and use it as a sort of entrypoint, inside which you call a real function (maybe private) that performs sorting, for example, in a recursive way or something elseWuhsien
Even though you have already accepted an answer, I still posted one providing you with another method or approach to resolving the issue of overloading functions with different argument counts.Righthand
W
5

As stated in the comments, you have to use the same signature for all your overrides. In doing so, you could use the following approach: use overridden function as a sort of entrypoint, inside which you call a real function (maybe private) that performs sorting. An example to illustrate the approach:

class SortingAlgo
{
public:
    virtual void sort(int arr[], int n) = 0;
};

class BubbleSort: public SortingAlgo
{
public:
    void sort(int arr[], int n){
        this->bubble_sort(arr, n);
    }
private:
    void bubble_sort(int arr[], int n){
        //implemetation
    }
};

class MergeSort: public SortingAlgo
{
public:
    void sort(int arr[], int n){
        this->mergeSort(arr, 0, n - 1);
    }
private:
    void mergeSort(int arr[], int l, int r){
        //recursive implemetation
    }
    void merge(int arr[], int l, int m, int r){
        //implemenation
    }
};
Wuhsien answered 24/8, 2020 at 19:18 Comment(0)
R
-1

Here's another approach to solving the problem of overloading virtual methods that have a different amount of arguments. This will work provided that the parameter-types are of the same type. If the types differ, then you may have to apply templates and use template-type deduction or use variadic function templates...

Here is the example code and the generated assembly compiled with GCC 10.2 which can be seen on Compiler Explorer.

CPP

class Base {
public:
    virtual void foo(int a, int b, int c, int d) = 0;
};

class Bar : public Base {
public:
    // Requires All 4 Argumements - Don't Deafult any Parameter
    virtual void foo(int a, int b, int c, int d) override {
        // Use every parameter for this version of the function
    }
};

class Baz : public Base {
public:
    // Requires 3 Arguments - Default the Last Parameter
    virtual void foo(int a, int b, int c, int d = 0) override {
        // Use the first three parameters within this version of the function. 
    }
};

class Fiz : public Base {
public:
    // Requires 2 Arguments - Default the Last 2 Parameters
    virtual void foo(int a, int b = 0, int c = 0, int d = 0) override {
        // Use the first two parameters within this version of the function.   
    }
};

class Buz : public Base {
public:
    // Requires Only 1 Agument - Default All but the 1st Parameter
    virtual void foo(int a, int b = 0, int c = 0, int d = 0) override {
        // Use only the first parameter within this version of the function.
    }
};


int main() {
    Bar bar;
    bar.foo(1,2,3,4);
    Baz baz;
    baz.foo(5,6,7);
    Fiz fiz;
    fiz.foo(8,9);
    Buz buz;
    buz.foo(10);

    return 0;
} 

ASM

Bar::foo(int, int, int, int):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     DWORD PTR [rbp-12], esi
        mov     DWORD PTR [rbp-16], edx
        mov     DWORD PTR [rbp-20], ecx
        mov     DWORD PTR [rbp-24], r8d
        nop
        pop     rbp
        ret
Baz::foo(int, int, int, int):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     DWORD PTR [rbp-12], esi
        mov     DWORD PTR [rbp-16], edx
        mov     DWORD PTR [rbp-20], ecx
        mov     DWORD PTR [rbp-24], r8d
        nop
        pop     rbp
        ret
Fiz::foo(int, int, int, int):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     DWORD PTR [rbp-12], esi
        mov     DWORD PTR [rbp-16], edx
        mov     DWORD PTR [rbp-20], ecx
        mov     DWORD PTR [rbp-24], r8d
        nop
        pop     rbp
        ret
Buz::foo(int, int, int, int):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     DWORD PTR [rbp-12], esi
        mov     DWORD PTR [rbp-16], edx
        mov     DWORD PTR [rbp-20], ecx
        mov     DWORD PTR [rbp-24], r8d
        nop
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 32
        mov     eax, OFFSET FLAT:vtable for Bar+16
        mov     QWORD PTR [rbp-8], rax
        lea     rax, [rbp-8]
        mov     r8d, 4
        mov     ecx, 3
        mov     edx, 2
        mov     esi, 1
        mov     rdi, rax
        call    Bar::foo(int, int, int, int)
        mov     eax, OFFSET FLAT:vtable for Baz+16
        mov     QWORD PTR [rbp-16], rax
        lea     rax, [rbp-16]
        mov     r8d, 0
        mov     ecx, 7
        mov     edx, 6
        mov     esi, 5
        mov     rdi, rax
        call    Baz::foo(int, int, int, int)
        mov     eax, OFFSET FLAT:vtable for Fiz+16
        mov     QWORD PTR [rbp-24], rax
        lea     rax, [rbp-24]
        mov     r8d, 0
        mov     ecx, 0
        mov     edx, 9
        mov     esi, 8
        mov     rdi, rax
        call    Fiz::foo(int, int, int, int)
        mov     eax, OFFSET FLAT:vtable for Buz+16
        mov     QWORD PTR [rbp-32], rax
        lea     rax, [rbp-32]
        mov     r8d, 0
        mov     ecx, 0
        mov     edx, 0
        mov     esi, 10
        mov     rdi, rax
        call    Buz::foo(int, int, int, int)
        mov     eax, 0
        leave
        ret
vtable for Buz:
        .quad   0
        .quad   typeinfo for Buz
        .quad   Buz::foo(int, int, int, int)
vtable for Fiz:
        .quad   0
        .quad   typeinfo for Fiz
        .quad   Fiz::foo(int, int, int, int)
vtable for Baz:
        .quad   0
        .quad   typeinfo for Baz
        .quad   Baz::foo(int, int, int, int)
vtable for Bar:
        .quad   0
        .quad   typeinfo for Bar
        .quad   Bar::foo(int, int, int, int)
typeinfo for Buz:
        .quad   vtable for __cxxabiv1::__si_class_type_info+16
        .quad   typeinfo name for Buz
        .quad   typeinfo for Base
typeinfo name for Buz:
        .string "3Buz"
typeinfo for Fiz:
        .quad   vtable for __cxxabiv1::__si_class_type_info+16
        .quad   typeinfo name for Fiz
        .quad   typeinfo for Base
typeinfo name for Fiz:
        .string "3Fiz"
typeinfo for Baz:
        .quad   vtable for __cxxabiv1::__si_class_type_info+16
        .quad   typeinfo name for Baz
        .quad   typeinfo for Base
typeinfo name for Baz:
        .string "3Baz"
typeinfo for Bar:
        .quad   vtable for __cxxabiv1::__si_class_type_info+16
        .quad   typeinfo name for Bar
        .quad   typeinfo for Base
typeinfo name for Bar:
        .string "3Bar"
typeinfo for Base:
        .quad   vtable for __cxxabiv1::__class_type_info+16
        .quad   typeinfo name for Base
typeinfo name for Base:
        .string "4Base"

As I have already said, this will work in simple cases, when the types for each argument-parameter placement within the function declaration-definitions are the same type. The trick here is to take the version of the function that has the highest number of arguments and use that in the declaration of the pure-virtual function within the abstract base class.

Now you can still do this if the types are different, but their placements must match for example:

class Base {
public:
    virtual void foo(int a, double b, char c) = 0;
};

class Bar : public Base {
public:
    virtual void foo(int a, double b, char c) override { } // uses all three
};

class Baz : public Base {
public:
    virtual void foo(int a, double b, char c = '') override {} // uses only the int and double
};

class Biz : public Base {
public:
    virtual void foo(int a, double = 0.0, char c = '') override{} // uses only the int
};

This should work too, but I haven't tested it.

Righthand answered 25/8, 2020 at 5:40 Comment(2)
Given what OP is asking for, suggesting to apply an arbitrary number of arguments that serve no purpose to many implementations is a massive design smell. This is trying to a force the triangle-shaped abstraction into a square-shaped hole. Don't do that. This does not scale if a new type suddenly needs 5 arguments. What happens then? Update all implementations to default the 5th argument? It's better to design a proper abstraction that is free from these problems, than forcing it to work in the wrong abstraction to begin withNonstandard
@Human-Compiler I understand what you are saying, this design is okay for types that you know will never change and are closely related. This is not a 1 size fits all and it won't be suitable for every situation. I was only suggesting it because it does work for simple cases. Consider working with a 3D Scene where you have an Abstract Light class. From it, you have 3 derived class types: Point, Directional, and Spot. They each take a different amount of parameters, the number of parameters will never change and you'll only ever have 3 light types... Therefore there will never be a "new" type.Righthand

© 2022 - 2024 — McMap. All rights reserved.