4

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...
}
manlio
  • 18,345
  • 14
  • 76
  • 126
Andrew
  • 1,581
  • 3
  • 18
  • 31
  • You cannot call virtual functions from a destructor. Well, you can, but the result will not be what you might expect. So just don't do it. – n. m. could be an AI Jan 27 '13 at 16:37
  • 1
    can you post the declarations of the class? Which are the virtual functions? – Emanuele Paolini Jan 27 '13 at 16:39
  • why not try to **debug** the code? – Cheers and hth. - Alf Jan 27 '13 at 16:47
  • 1
    @n.m.: your comment is absolutely, fundamentally wrong. virtual functions can be called from constructors and destructors. and as opposed to languages like Java and C#, in C++ this is safe to do (and does exactly what **I** expect, but maybe not what someone who doesn't know the language expects). – Cheers and hth. - Alf Jan 27 '13 at 16:48
  • This code does not seem to have any calls to object's member functions from its destructor. – n. m. could be an AI Jan 27 '13 at 16:53
  • @Cheersandhth.-Alf: **You** can do it, as long as such code doesn't have to go through my code review. It will not pass, because it violates the *principle of least surprise*. – n. m. could be an AI Jan 27 '13 at 16:58
  • @Cheersandhth.-Alf: though if you call a virtual function by its qualified name, and explain why in a comment, that's fine with me. – n. m. could be an AI Jan 27 '13 at 17:08
  • @n.m.: you're wrong in every respect, including your silly belief that an authority argument fallacy based on alleged managerial position would impress anybody. it doesn't. it's just idiotic. – Cheers and hth. - Alf Jan 27 '13 at 17:12
  • @Cheersandhth.-Alf: thank you for your thoughtful and polite comment. Now if you elect to provide some actual arguments in favor of your position, that would be impressive. – n. m. could be an AI Jan 27 '13 at 17:27
  • @Cheersandhth.-Alf: Dear I have added information with my ans below with links which back that we should not call virtual function during cons/dest, have a look. Just a comment instead of getting hyper, why not read books and verify on compiler ? :) If you spend time on reading books instead of backing up something about which you don't know, it would be fruitful for you and all other :) – Saqlain Jan 27 '13 at 17:59
  • @Saqlain: Although Alf comments are generally inflammatory (or high-handed), he does know C++ well (as is reputation attests) and in this precise case, he is right. The C++ Standard fully specifies what will happen should you call a virtual function from a constructor or destructor; and therefore it is *safe* in that regard. It also specifies that calling a pure virtual function is *undefined behavior* by the way. – Matthieu M. Jan 27 '13 at 18:26
  • 1
    @MatthieuM. Yes of course the standard fully specifies what will happen in this case. This is not an excuse to use this language construct in your actual code. It is important to know what the standard says. It is also important to know what a novice programmer assigned to maintain your code 5 years down the road will do with it when you, the author, is not around any more. – n. m. could be an AI Jan 27 '13 at 18:50
  • @n.m.: Ah, but that is another point completely; and for the record I agree that one should aim to keep its codebase simple enough when practical. – Matthieu M. Jan 27 '13 at 18:53

4 Answers4

12

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

Saqlain
  • 17,490
  • 4
  • 27
  • 33
  • 1
    -1 "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" is incorrect. Also, the language comparison is strongly misleading. This is a trolling answer. – Cheers and hth. - Alf Jan 27 '13 at 17:17
  • 1
    also "you may call some cleanup function before object get destroyed" is extremely bad advice: multi-phase destruction is just as bad as multi-phase construction. – Cheers and hth. - Alf Jan 27 '13 at 17:18
  • 4
    A person who know something about C++ would agree with me for sure :-) what i said is also mentioned in Effective C++, Third Edition by Scott Meyers, better to spend some time reading books then wasting time on commenting over stackoverflow :-) – Saqlain Jan 27 '13 at 17:29
  • 1
    You write "A person who know something about C++ would agree" ... That implies that I've said you don't know anything about C++, which while not an outright lie is very much in that direction. Also, it is an authority argument, which is a fallacy, a pretty stupid argument to offer since the first fallacy tells the reader that you yourself think you're wrong (whether you are or not). Instead of diagnosing a general incompetence (as you wrongfully imply), I've written that this answer, with its carefully selected incorrect but plausible claims, is a trolling one. – Cheers and hth. - Alf Jan 27 '13 at 17:35
  • I will again suggest to read some books instead of spreading wrong information i still back my point virtual function should never be called during const/dest, dont go else where just read Effective C++, Third Edition by Scott Meyers :) exactly same topic being discussed in this book also... – Saqlain Jan 27 '13 at 17:42
  • 2
    in the linked to material, Scott writes: "This seems like a reasonable way to approach the problem", about a call to a pure virtual function in destructor. It isn't, it doesn't seem reasonable, except for the totally inept. I.e. Scott is simply wrong here; what he writes, is not factually true. Scott Meyers has contributed a lot to C++, including the Meyers' singleton and the original comments that removed auto-generation of move constructors and assignment operators. But he's also been fundamentally wrong about some things, and here he is. So, forget that piece as authority. It's incorrect. – Cheers and hth. - Alf Jan 27 '13 at 18:02
  • But dear i exactly faced same problem in past and it sort out to this calling behavior diff at runtime:) any way have a nice weekend. – Saqlain Jan 27 '13 at 18:07
  • 1
    you might think about it this way: in C++ you don't pay for what you don't need, as a rule, but for construction and destruction there's a lot of work going on to adjust the dynamic type of the object according to how constructed or destroyed it is at any time. and this is only to ensure that virtual calls *work* as intended. in java and c# it's not done, so in those languages you risk accessing not-initialized or already-destroyed parts. but in c++ it's safe, and that's paid for by a bit of efficiency. which would never have made it into the language design if you shouldn't use it. – Cheers and hth. - Alf Jan 27 '13 at 18:16
  • 1
    hmm, i got CPP standard infront of me http://e-maxx.ru/bookz/files/cpp_standard.pdf, read section which says, cant paste here "... 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...", it do contradict what you saying ...and it declare it as undefine behavior or forbidden :) – Saqlain Jan 27 '13 at 18:26
  • 1
    @Saqlain: it is only undefined for **pure** virtual functions. Otherwise it is defined, and the dynamic type of the object is considered to be the current (static) type. Meyers' books are mostly aimed at beginners, to teach them simplified rules that allow them to avoid dangerous areas of C++, they are rule of thumbs; and that is a very good thing; but you need to carefully assess whether they are still beneficial after you are sufficiently comfortable with the language. – Matthieu M. Jan 27 '13 at 18:49
7

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.

Community
  • 1
  • 1
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • -1 "if you call a virtual function in a constructor or destructor, then the function is not dynamically dispatched; the function called is that of the current class" is wrong. it will not be an implementation in a derived class, but that's not what the quoted text claims. yell if you want example, provide chapter and verse that confused you if you want discussion. – Cheers and hth. - Alf Jan 27 '13 at 23:34
  • @Cheersandhth.-Alf: I provided the chapters and verses myself, is it the formulation that bothers you ? (I am open to suggestions). – Matthieu M. Jan 28 '13 at 07:43
  • "final overrider" does not mean what you parahprase it as. The function calls are dynamically dispatched. What you write is not what the standard says, what you write does not mean the same as what you quote from the standard, and what you write would make much C++ code stop working. It has nothing to do with mere formulation. It's utterly and completely wrong. – Cheers and hth. - Alf Jan 28 '13 at 08:07
  • @Cheersandhth.-Alf: I see what you mean, I reworded the explanation to be closer to the Standard and catter to the case where a `virtual` function is called through a base class reference. I am still not quite satisfied with it (too obtuse I think), and so still open to suggestions. – Matthieu M. Jan 28 '13 at 08:22
  • 1
    I think it's fine now. For another formulation, during construction and destruction of class T, the dynamic type is T, so that calls *behave the same* as if the object was instantiated as T (which it might very well have been, in general). Anyway, that's fine now, but the "finalize" example in the answer is needlessly brittle. Instead of the Java-inspired "Managers" and "Singletons" and "BlahBlah" patterns, etc., instead of that just introduce a class derived from the class in question, and do destruction things in the destructor. That's what it's for. – Cheers and hth. - Alf Jan 28 '13 at 08:29
5

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.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
0

Depending on what system you're on this may work:

Set a breakpoint on the pure function call handler: __cxa_pure_virtual.

This worked for me using gdb: break __cxa_pure_virtual then, when the breakpoint was hit, up showed my code that was busted. (In my case, I am introducing a second thread and destruction was currently happening on a different thread while the object was being used.)

Documentation of __cxa_pure_virtual: https://www.swag.uwaterloo.ca/acd/docs/ItaniumC++ABI.htm#pure-virtual

lmat - Reinstate Monica
  • 7,289
  • 6
  • 48
  • 62