How to fix pure virtual function called runtime er

2019-05-10 10:57发布

I understand why I am getting the error I am getting (pure virtual function called). I am trying to call pure virtual functions from within the destructor of my base class shown below. However, I do not know how to rework my code to prevent this from happening. Here are the base and derived classes (the relevant portions anyway):

Base class:

TailFileManager::TailFileManager(const std::string &filename, const int fileOpenPeriod_ms)
: m_Stop(false)
{
    m_WorkerThread.reset(new boost::thread(boost::bind(&TailFileManager::TailFile, this, filename, fileOpenPeriod_ms)));
}

TailFileManager::~TailFileManager()
{
    m_Stop = true;
    m_WorkerThread->join();
}

void TailFileManager::TailFile(const std::string &filename, const int fileOpenPeriod_ms)
{
    std::ifstream ifs(filename.c_str());

    while (! ifs.is_open())
    {
        boost::this_thread::sleep(boost::posix_time::milliseconds(fileOpenPeriod_ms));
    ifs.open(filename.c_str());
    }

    ifs.seekg(0, std::ios::end);

    while (! m_Stop)
    {
        ifs.clear();

        std::string line;

        while (std::getline(ifs, line))
        {
            OnLineAdded(line);
        }

        OnEndOfFile();
    }

    ifs.close();
}

Derived class:

ETSLogTailFileManager::ETSLogTailFileManager(const std::string &filename, const int heartbeatPeriod_ms)
: TailFileManager(filename, heartbeatPeriod_ms),
  m_HeartbeatPeriod_ms(heartbeatPeriod_ms),
  m_FoundInboundMessage(false),
  m_TimeOfLastActivity(0)
{
}

ETSLogTailFileManager::~ETSLogTailFileManager()
{
}

void ETSLogTailFileManager::OnLineAdded(const std::string &line)
{
    // do stuff...
}

void ETSLogTailFileManager::OnEndOfFile()
{
    // do stuff...
}

3条回答
戒情不戒烟
2楼-- · 2019-05-10 11:25

You write,

“I am trying to call pure virtual functions from within the destructor of my base class shown below.”

And the code in question is

TailFileManager::~TailFileManager()
{
    m_Stop = true;
    m_WorkerThread->join();
}

Happily in a single-threaded execution this couldn't possibly call a pure virtual function. But the thread that you're joining might call a pure virtual function on this object, possibly via a non-virtual member function. If so, then the issue is with the threading, specifically the lifetime management of this object.

Unfortunately you do not show the relevant code. Try to reduce things to a small, complete, working example. Where "working" in the sense that it reproduces the problem.

查看更多
啃猪蹄的小仙女
3楼-- · 2019-05-10 11:28

As far as the C++ standard is concerned:

  • if you call a virtual function in a constructor or destructor, then the function is dynamically dispatched as if its dynamic type were that of the current constructor/destructor being executed (§12.7/4)
  • if that function happened to a pure virtual, then this is undefined behavior (§10.4/6); the Itanium ABI defines the behavior: __cxa_pure_virtual is called.

So, you have a bit of a thorny issue...


A possible solution to the problem would be to break it down in two parts, in order to break the destruction in two parts. This could be achieved with a Strategy pattern:

  • provide a customizable interface, your strategy
  • provide a manager class that encapsulate the functionality and defers to the strategy for the customizable parts

Let's make it clearer:

class Interface {
public:
    friend class Manager;

private:
    virtual void finalize() = 0;
}; // class Interface


class Manager {
public:
    explicit Manager(std::unique_ptr<Interface>&&);

    ~Manager();

private:
    std::unique_ptr<Interface> _interface;
}; // class Manager

Manager::~Manager() {
    _interface->finalize();
}

The trick ? At the point where finalize() is called the destruction of _interface has not begun yet! The call to the destructor will happen later; and thus you do not suffer from a half-dead object's fate.

I'll end this answer by a warning about join-ing a thread in a destructor now. Beware that destructors are automatically called in case of stack unwinding, it might therefore be dangerous to wait indefinitely while failing; especially if the thread is waiting for data that should be provided by the currently being unwound one... a classic case of dead-lock.


References (n3337):

§12.7/4 Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class’s non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more-derived class.

§10.4/6 Member functions can be called from a constructor (or destructor) of an abstract class; the effect of making a virtual call (10.3) to a pure virtual function directly or indirectly for the object being created (or destroyed) from such a constructor (or destructor) is undefined.

查看更多
小情绪 Triste *
4楼-- · 2019-05-10 11:38

You shouldn't call virtual functions during construction or destruction, because the calls won't do what you think, and if they did, you'd still be unhappy. If you're a recovering Java or C# programmer, pay close attention to this Item, because this is a place where those languages zig, while C++ zags.

Re-work your design i.e you may call some cleanup function before object get destroyed, idea is just avoid virtual function during const/dest (if there are any!), if you are working with C++...

The rules for virtual invocation are different. C++ 2003, section 12.7 "Construction and Destruction", says:

Lets refresh some old memories ...

Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). When a virtual function is called directly or indirectly from a constructor (including from the mem-initializer for a data member) or from a destructor, and the object to which the call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructorâs own class or in one of its bases, but not a function overriding it in a class derived from the constructor or destructorâs class, or overriding it in one of the other base classes of the most derived object (1.8). If the virtual function call uses an explicit class member access (5.2.5) and the object-expression refers to the object under construction or destruction but its type is neither the constructor or destructorâs own class or one of its bases, the result of the call is undefined.

Because of this difference in behavior, it is recommended that you never invoke an object's virtual function while it is being constructed or destroyed.

Never Call Virtual Functions during Construction or Destruction An Excerpt from Effective C++, Third Edition by Scott Meyers June 6, 2005

http://www.artima.com/cppsource/nevercall.html

查看更多
登录 后发表回答