 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Dave Guest
|
Posted: Thu Jul 29, 2004 9:23 pm Post subject: Pure Virtual Function call error |
|
|
Hi,
I have tried to use one of Herb Sutters examples from his book
Exceptional C++ It is Item 23 and introducedsloads of cool concepts. I
have implemented the template pattern and then used the _pimpl idiom
and all was going well. He
then suggest splitting functionality further:
Generic.h
---------
class GenericClient;
class GenericAlgorithm
{
public:
GenericAlgorithm(CFDALoadCounterParty::ListType& myListin,
GenericClient& worker);
virtual ~GenericAlgorithm();
bool Process();
protected:
KbResultSetManager manager;
KbDbBinder binder;
vector<CExtRefSystemCode*> dave_list;
vector<CExtRefSystemCode*>::iterator dave_list_iterator;
private:
struct GenericAlgorithmImpl;
GenericAlgorithmImpl* pimpl_;
};
GenericClient.h
---------------
using namespace std;
#include "Generic.h"
class GenericClient
{
public:
virtual bool Filter( CExtRefSystemCode* );
virtual bool ProcessRow( CExtRefSystemCode* ) = 0;
virtual ~GenericClient() = 0 {};
protected:
KbResultSetManager manager;
KbDbBinder binder;
vector<CExtRefSystemCode*> dave_list;
vector<CExtRefSystemCode*>::iterator dave_list_iterator;
};
class MyWorker : public GenericClient
{
public:
bool Filter(CExtRefSystemCode*);
bool ProcessRow( CExtRefSystemCode*);
};
I have attempted to store the reference to GenericClient in the _pimpl
Generic.cpp
-----------
struct GenericAlgorithm::GenericAlgorithmImpl
{
public:
CFDALoadCounterParty::ListType m_list;
CFDALoadCounterParty::IterType m_iter;
GenericClient* pWorkerClient;
};
and have tried initialising it in the constructor:
GenericAlgorithm::GenericAlgorithm(CFDALoadCounterParty::ListType&
myListin, GenericClient& worker) : pimpl_( new GenericAlgorithmImpl()
)
{
pimpl_->m_list = myListin;
pimpl_->pWorkerClient = &worker;
}
The Client code constructs the GenericAlgorith object as follows
bool CFDALoadCounterParty::LoadIntoFDA()
{
GenericAlgorithm dave(m_list, MyWorker() );
dave.Process();
return true;
}
The problem occurs in the Process method when I try using the
pWorkerClient as it is pointing at a GenericClient object so when I
try calling
pimpl_->pWorkerClient->ProcessRow(*dave_list_iterator);
It fails internally as is tries calling a pure virtual function
I can kind of see where the problem lies but have been unable to make
the mental leap to fix it.
Any help greatly appreciated.
Many thanks in advance,
Dave.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Maxim Yegorushkin Guest
|
Posted: Fri Jul 30, 2004 9:51 am Post subject: Re: Pure Virtual Function call error |
|
|
Dave <david.brooks (AT) drkw (DOT) com> wrote:
[]
| Quote: | GenericClient.h
---------------
using namespace std;
#include "Generic.h"
class GenericClient
{
public:
virtual bool Filter( CExtRefSystemCode* );
virtual bool ProcessRow( CExtRefSystemCode* ) = 0;
virtual ~GenericClient() = 0 {};
protected:
KbResultSetManager manager;
KbDbBinder binder;
vector<CExtRefSystemCode*> dave_list;
vector<CExtRefSystemCode*>::iterator dave_list_iterator;
};
class MyWorker : public GenericClient
{
public:
bool Filter(CExtRefSystemCode*);
bool ProcessRow( CExtRefSystemCode*);
};
|
Please note, that MyWorker is still an abstract class that you can't instantiate, because it does not implement destructor which is defined as
pure virtual in GenericClient class.
[]
he Client code constructs the GenericAlgorith object as follows
| Quote: |
bool CFDALoadCounterParty::LoadIntoFDA()
{
GenericAlgorithm dave(m_list, MyWorker() );
dave.Process();
return true;
}
|
You are using a nonconformant compiler that allows you to instantiate abstract classes and bind r-values to non-const references (MSVC6?). What
happens is this:
GenericAlgorithm dave(m_list, MyWorker() );
here you instantiate (uninstantiable) temporary MyWorker instance which you pass to GenericAlgorithm which (illegally) binds it to a non-const
reference. After dave object has been constructed the temporary MyWorker instance is destroyed which destructor effectively reassigns its vptr
to the virtual table of the base class which is GenericClient. So, your dave object is left with a pointer to a nonexisting instance of MyWorker
whose vptr points to GenericClient vtable with pure virtual ProcessRow.
To fix the code implement the destructor in MyWorker and rewrite LoadIntoFDA as follows:
{
MyWorker wrkr;
GenericAlgorithm dave(m_list, wrkr);
dave.Process();
return true;
}
--
Maxim Yegorushkin
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Ulrich Eckhardt Guest
|
Posted: Fri Jul 30, 2004 9:51 am Post subject: Re: Pure Virtual Function call error |
|
|
Dave wrote:
| Quote: | class GenericClient;
class GenericAlgorithm
{
public:
GenericAlgorithm(CFDALoadCounterParty::ListType& myListin,
GenericClient& worker);
virtual ~GenericAlgorithm();
bool Process();
protected:
KbResultSetManager manager;
KbDbBinder binder;
vector<CExtRefSystemCode*> dave_list;
vector<CExtRefSystemCode*>::iterator dave_list_iterator;
private:
struct GenericAlgorithmImpl;
GenericAlgorithmImpl* pimpl_;
};
|
First thing, consider not using tabs for indenting, as it simply is not
portable(as the messed up layout shows...). Anyhow, there are already two
basic rules violated in this code:
1. law of three: implement ctor, cctor and assignment operator or none of
them. GenericAlgorithm objects are copyable as it stands.
2. virtual dtor: you have one, but that is useless since you don't have any
virtual methods. Your class is not means for derivation, right?
| Quote: | GenericClient.h
using namespace std;
|
*niarg* Forcing all user of your class to also include std into their
global namespace...
| Quote: | GenericAlgorithm::GenericAlgorithm(CFDALoadCounterParty::ListType&
myListin, GenericClient& worker) : pimpl_( new GenericAlgorithmImpl()
)
{
pimpl_->m_list = myListin;
pimpl_->pWorkerClient = &worker;
}
The Client code constructs the GenericAlgorith object as follows
bool CFDALoadCounterParty::LoadIntoFDA()
{
GenericAlgorithm dave(m_list, MyWorker() );
dave.Process();
return true;
}
|
*WoW* I really love those functions that always return true. That way you
never have to check their returnvalue. ;)
Seriously, What I wonder is whether 'MyWorker' is a type or a function. If
it's a type, I daresay you are not showing the code you are compiling.
Else, I wonder how long the object it returns will remain alive.
| Quote: | The problem occurs in the Process method when I try using the
pWorkerClient as it is pointing at a GenericClient object so when I
try calling
pimpl_->pWorkerClient->ProcessRow(*dave_list_iterator);
It fails internally as is tries calling a pure virtual function
|
The pointer might be a 'GenericClient*', but that should not be the cause
of your problem as using it this way is the intended use for it and the
base of OOP in C++.
There are only two ways to call a pure virtual function erroneously: while
in the ctor and while in the dtor. In both cases, the object is not fully
constructed (or already partially destroyed), but AFAICT neither is the
case here.
Now, the only case left where I stumbled across this is when someone had a
dangling pointer, i.e. a pointer that once pointed to an object but that
object was already destroyed. Since you chose to disobey the rule of
three, you have to make sure manually that your classes are not misused;
I'd suggest fixing that first. Then, you can insert code that logs ctors
and dtors, and I'm sure you will find that you are operating on an object
that already ceased to exist. Also always be sure to turn on compiler
warnings.
Uli
--
Questions ?
see C++-FAQ Lite: http://parashift.com/c++-faq-lite/ first !
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Dave Guest
|
Posted: Sat Jul 31, 2004 3:10 am Post subject: Re: Pure Virtual Function call error |
|
|
"Maxim Yegorushkin" <e-maxim (AT) yandex (DOT) ru> wrote
| Quote: | Dave <david.brooks (AT) drkw (DOT) com> wrote:
[]
GenericClient.h
---------------
using namespace std;
#include "Generic.h"
class GenericClient
{
public:
virtual bool Filter( CExtRefSystemCode* );
virtual bool ProcessRow( CExtRefSystemCode* ) = 0;
virtual ~GenericClient() = 0 {};
protected:
KbResultSetManager manager;
KbDbBinder binder;
vector<CExtRefSystemCode*> dave_list;
vector<CExtRefSystemCode*>::iterator dave_list_iterator;
};
class MyWorker : public GenericClient
{
public:
bool Filter(CExtRefSystemCode*);
bool ProcessRow( CExtRefSystemCode*);
};
Please note, that MyWorker is still an abstract class that you can't instantiate, because it does not implement destructor which is defined as
pure virtual in GenericClient class.
[]
he Client code constructs the GenericAlgorith object as follows
bool CFDALoadCounterParty::LoadIntoFDA()
{
GenericAlgorithm dave(m_list, MyWorker() );
dave.Process();
return true;
}
You are using a nonconformant compiler that allows you to instantiate abstract classes and bind r-values to non-const references (MSVC6?). What
happens is this:
GenericAlgorithm dave(m_list, MyWorker() );
here you instantiate (uninstantiable) temporary MyWorker instance which you pass to GenericAlgorithm which (illegally) binds it to a non-const
reference. After dave object has been constructed the temporary MyWorker instance is destroyed which destructor effectively reassigns its vptr
to the virtual table of the base class which is GenericClient. So, your dave object is left with a pointer to a nonexisting instance of MyWorker
whose vptr points to GenericClient vtable with pure virtual ProcessRow.
To fix the code implement the destructor in MyWorker and rewrite LoadIntoFDA as follows:
{
MyWorker wrkr;
GenericAlgorithm dave(m_list, wrkr);
dave.Process();
return true;
}
|
Maxim,
many thanks for your comments. I did implement MyWorker methods so it
was no longer an abstract class.
After posting this I played around with it a bit more and came to the
solution you suggested - which fixed it. I then played around with it
some more and returned to the code in my original posting.
Believe it or not that magically worked ?????!!!!
Cheers,
Dave.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
David Olsen Guest
|
Posted: Sun Aug 01, 2004 2:22 am Post subject: Re: Pure Virtual Function call error |
|
|
Your problem is that you are keeping a pointer to a temporary object,
and dereferencing that pointer after the temporary object has been
destroyed. That's undefined behavior, so anything could happen. In
your case, the symptom just happens to be a pure virtual function call.
| Quote: | GenericAlgorithm::GenericAlgorithm(CFDALoadCounterParty::ListType&
myListin, GenericClient& worker) : pimpl_( new GenericAlgorithmImpl()
)
{
pimpl_->m_list = myListin;
pimpl_->pWorkerClient = &worker;
/* Storing a pointer to the second argument */ |
| Quote: | }
bool CFDALoadCounterParty::LoadIntoFDA()
{
GenericAlgorithm dave(m_list, MyWorker() );
/* But the second argument is a temporary object, which is |
destroyed at the end of the statement */
| Quote: | dave.Process();
/* The pointer that was saved is dereferenced here, accessing |
an object that has been destroyed. */
--
David Olsen
[email]qg4h9ykc5m (AT) yahoo (DOT) com[/email]
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
David Olsen Guest
|
Posted: Sun Aug 01, 2004 2:22 am Post subject: Re: Pure Virtual Function call error |
|
|
Maxim Yegorushkin wrote:
| Quote: | Dave <david.brooks (AT) drkw (DOT) com> wrote:
GenericClient.h
---------------
using namespace std;
#include "Generic.h"
class GenericClient
{
public:
virtual bool Filter( CExtRefSystemCode* );
virtual bool ProcessRow( CExtRefSystemCode* ) = 0;
virtual ~GenericClient() = 0 {};
protected:
KbResultSetManager manager;
KbDbBinder binder;
vector<CExtRefSystemCode*> dave_list;
vector<CExtRefSystemCode*>::iterator dave_list_iterator;
};
class MyWorker : public GenericClient
{
public:
bool Filter(CExtRefSystemCode*);
bool ProcessRow( CExtRefSystemCode*);
};
Please note, that MyWorker is still an abstract class that you can't
instantiate, because it does not implement destructor which is
defined as pure virtual in GenericClient class.
|
MyWorker is not an abstract class. Since there is no user-defined
destructor, the compiler will automatically declare and define a
destructor for class MyWorker, just like it does for any other class.
This automatically-defined destructor overrides the pure virtual
destructor in GenericClient.
--
David Olsen
[email]qg4h9ykc5m (AT) yahoo (DOT) com[/email]
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Maxim Yegorushkin Guest
|
Posted: Sun Aug 01, 2004 2:28 pm Post subject: Re: Pure Virtual Function call error |
|
|
David Olsen <qg4h9ykc5m (AT) yahoo (DOT) com> wrote:
| Quote: | MyWorker is not an abstract class. Since there is no user-defined
destructor, the compiler will automatically declare and define a
destructor for class MyWorker, just like it does for any other class.
This automatically-defined destructor overrides the pure virtual
destructor in GenericClient.
|
Sorry, my mistake.
--
Maxim Yegorushkin
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|