Dear Readers!

Today I’m going to share a trick for creating a C++ abstract base class, when you don’t really have a custom function to declare as pure virtual. Such a situation could occur, when you just want to have a collection of structs with some common member variable. Unfortunately for us, standard C++ has no “abstract” or “interface” or similar keywords to help us out in such cases, so let’s see what we can do. Here’s a first try:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct IBase
{
    explicit IBase(const std::string& baseVal)
        : mBaseVal(baseVal) {}
    
    virtual ~IBase() {}
    
    std::string mBaseVal;
};

struct Derived : public IBase
{
    Derived()
        : IBase("MyObjStuff") {}
        
    std::string mDerivedVal;
};

int main() {

    IBase base("Base value");  // This shouldn't build
    
    return 0;
}

This seems fine, but unfortunaley for us, it will compile, since we do not have any pure virtuals. One fix could be to make mBaseVal private and create a pure virtual getter for it, but that would result in uncecessary extra complexity. Since the only function that we do have (which we can make into virtual) is the destructor, let’s make that pure virtual:

C++

1
2
3
4
5
6
7
8
9
struct IBase
{
    explicit IBase(const std::string& baseVal)
        : mBaseVal(baseVal) {}
    
    virtual ~IBase() = 0;
    
    std::string mBaseVal;
};

This is better, but now we do not have an implementation to be called from derived classes (which is always dangerous), and now it won’t even compile, but not for the reasons we want:

Shell

... in function `Derived::~Derived()':
... undefined reference to `IBase::~IBase()'
collect2: error: ld returned 1 exit status

So what can be done? To my surprise, the language actually does not prevent you from providing an implementation for pure virtual functions. Moreover, it is completely legal to do so as long as you are careful enough to provide the implementation outside the class:

C++

1
2
3
4
5
6
7
8
9
10
11
struct IBase
{
    explicit IBase(const std::string& baseVal)
        : mBaseVal(baseVal) {}
    
    virtual ~IBase() = 0;
    
    std::string mBaseVal;
};

IBase::~IBase() {}

Now everything should work as expected, as IBase cannot be instantiated any more. My only concern was to check if mBaseVal’s destructor will still get called with this setup (as far as I know it should, but better be safe than sorry), so let’s replace std::string with something of our own:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
struct MyObj
{
    explicit MyObj(const std::string& objVal)
        : mObjVal(objVal) {}
    
    ~MyObj() {std::cout << "MyObj destroyed\n";}
    
    std::string mObjVal;
};

std::ostream& operator<<(std::ostream& os, const MyObj& obj)
{
    os << obj.mObjVal;
    return os;
}

struct IBase
{
    explicit IBase(const std::string& baseVal)
        : mBaseVal(baseVal) {}
    
    virtual ~IBase() = 0;
    
    MyObj mBaseVal;
};

IBase::~IBase() {std::cout << "IBase destroyed\n";}

struct Derived : public IBase
{
    Derived()
        : IBase("base val") {}
        
    std::string mDerivedVal;
};

int main() {

    Derived derived;
    derived.mDerivedVal = "derived val";
    std::cout << derived.mDerivedVal << " " << derived.mBaseVal << std::endl;

    return 0;
}

The code above will lead to the desired result:

Shell

derived val base val
IBase destroyed
MyObj destroyed

So the lesson of the day is:

In case you need an abstract class but can’t define a common pure virtual function, make the destructor pure virtual instead and provide its definition outside the class.