0

I have an abstract base class that has the logic that handles the lifecycle of a thread (start, stop, join). The work executed in the thread depends on the derived class that is instantiated, and there are several derived classes.

The base class looks like this:

class Base {
public:
    Base(int i, bool b) : i{i}, b{b}
    {
        start();
    }

    virtual bool getB() { return b; };
    void stop() { stopWorking = true; workerTh.join(); };

protected:
    virtual void start()
    {
        std::cout << "Base start method" << std::endl;
        workerTh = std::thread{ std::bind(&Derived::work, this) };
    };
    virtual void work() = 0;

    std::thread workerTh;
    int i;
    bool b;

    bool stopWorking = false;
};

A derived class looks like this:

class Derived : public Base {
public:
    Derived(int i, bool b) : Base(i,b) {};

protected:
    void work()
    {
        std::cout << "Derived Work started!" << std::endl;
        while (not stopWorking)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }
        std::cout << "Derived Work ended!" << std::endl;
    }
};

It runs as expected in a regular program like:

int main()
{ 
    std::cout << "Starting" << std::endl;
    Derived d { 10, false};
    std::this_thread::sleep_for(std::chrono::milliseconds(2000));
    std::cout << "Finishing" << std::endl;
    d.stop();
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}

But if I execute it in a google test then I get a coredump and the cause is that I'm invoking a pure virtual method in this line:

workerTh = std::thread{ std::bind(&Base::work, this) };

So, why does that line work as expected in a normal program, but crashes in a google test? Am I missing something when launching GTest?

This is the test:

#include "BaseDerived.cc"
#include "gtest/gtest.h"

TEST(BaseDerivedWithThreadTest, FailsCoredumpCallingPureVirtual) {
    Derived d { 10, false };
    ASSERT_FALSE (d.getB());
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from BaseDerivedWithThreadTest
[ RUN      ] BaseDerivedWithThreadTest.FailsCoredumpCallingPureVirtual
Base start method
terminate called without an active exception
pure virtual method called
terminate called recursively
Aborted (core dumped)

I can overcome this issue by putting the thread lifecycle code down to the derived classes, but I find it very ugly just to make googletest happy.

Any ideas?

Thanks!

miguel perher
  • 901
  • 1
  • 8
  • 17
  • Two notes on the code: You're inconsistent in whether you place a semicolon after the closing curly brackets (`}`) of a function or not. Hint: They're completely unnecessary. Secondly, your code says that every `Child` is a `Parent`, which conflicts with common sense, where every parent is also a child, but not the other way around. Consider using `Base` and `Derived` instead, which doesn't have this problem. – Ulrich Eckhardt Dec 16 '19 at 18:27
  • Does this answer your question? [How to resolve "pure virtual method called"](https://stackoverflow.com/questions/10707286/how-to-resolve-pure-virtual-method-called) – Ulrich Eckhardt Dec 16 '19 at 18:28

1 Answers1

2

Your code is unfortunately broken: In the baseclass constructor, you call start(), which is virtual. This will never invoke a derived class' start() method, because the dynamic type at the time of call in the constructor is not of the derived class. The same applies to the virtual work() function. Worse, depending on when the thread is scheduled, it may or may not invoke the derived class' function, because its type may have changed to the derived class' type by that time.

The test failures are just a red herring perhaps, your code is broken. Fix that first and then see if the tests still fail.

Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55
  • That may be it: Invoking the thread when 'this' stil isn't a rightful 'Derived' object. However, I'm still puzzled on why it works 100% in a regular program and fails 100% in gtest. – miguel perher Dec 17 '19 at 09:29