Seems like you need some basic lesson in C++ inheritance to understand what the compiler wants to tell you – here we go!
Many JUCE classes use the concept of inheritance. In case you don’t know about it here is a brief introduction, focused on virtual functions you seem to have struggles with. However I’d strongly suggest you to have a deeper read on that topic, because it’s a core feature of C++.
You can declare the following class
class Base
{
public:
// The constructor does nothing more than assigning the value
Base (int initialValue) : someValue (initialValue) {}
// a "normal" member function
int getValue() { return someValue; }
// A virtual function which defines a basic behaviour. A class inheriting from Base can
// decide if it wants to use this version or override it.
virtual void increaseValue() { ++someValue; }
// A pure virtual function (marked by the =0) which defines no behaviour. A class inheriting from
// Base has to override it in order to become a usable class
virtual void printValue() = 0;
private:
int someValue;
}
As the comments state, Base has three member functions but at this time trying to instantiate an instance of Base
in your code like declaring Base myBase
somewhere will lead to a compile error like Base::printValue is a pure virtual function
– just like you got. Why is that the case? Well because printValue is pure virtual, this means the compiler only knows that each class inheriting from Base has a function printValue
but Base
doesn’t deliver any functionality for printValue
. What we have to do to make use of it is to create a new class inheriting from Base
that defines some behaviour for that virtual function. This could look like this:
class Inherited1 : public Base // this tells that Inherited1 should have the same public interface as Base
{
public:
// As the constructor of Base requires a parameter, we must supply one to the base class. Let's
// just pass on a value given to the inherited constructor for now
Inherited1 (int initialValue) : Base (initialValue) {};
// Here we override the pure virtual function. Inherited1 can now be instantiated in your code as
// every member function is defined through either the base class or the inheriting class.
// Note that we can also use a function of base here (getValue) that still has the same function
// as declared in the base class
void printValue() override { std::cout << "Value from Inherited1 instance: " << getValue() << std::endl; }
}
But we could also override the other virtual member function if we wanted to:
class Inherited2 : public Base
{
public:
// This constructor just passes the double value passed in to the base constructor
Inherited2 (int initialValue) : Base (2 * initialValue) {};
// This class decides to multiply the internal value by 2 when increasing it
void increaseValue() override { someValue *= 2; }
void printValue() override { std::cout << "Value from Inherited2 instance: " << getValue() << std::endl; }
}
Now you can write this code:
int main()
{
Inherited1 i1 (0);
Inherited2 i2 (1);
i1.printValue(); // should output: Value from Inherited1 instance: 0
i2.printValue(); // should output: Value from Inherited2 instance: 2
// Now we can do something fancy: While we can't instantiate a Base object,
// we can declare both, references and pointers of the type Base and let them
// point to inherited instances
Base& b1 = i1; // This reference now references i1
Base* b2 = &i2; // This pointer now points to the memory address of i2
// Ase increaseValue and printValue were declared in the base class, we can call
// them on the Base class reference/pointer:
b1.increaseValue();
b2->increseValue();
b1.printValue(); // should output: Value from Inherited1 instance: 1
b2->printValue(); // should output: Value from Inherited2 instance: 4
}
What is handy with this approach, is that you can now write functions that take a reference or pointer to Base
as argument and call their functions and let the object passed in decide what happens when calling them.