PDA

View Full Version : PIMPL, the 2nd: pure virtual destructor in private base class.



Lykurg
5th April 2012, 09:35
Hi,

I fail to make the destructor of my PIMPL-Private base class pure virtual like QObjectData does. Since I don't know which information about the PIMPL is relevant or not here comes the "full" test case. (Basically at this point mostly equal to Q_DECLARE_PRIVATE...)

First one is working:


#ifndef FOO_H
#define FOO_H

#include <QtCore>

#define MYXXX_DECLARE_PRIVATE(Class) \
inline Class##Private* d_myxxx_func() { return reinterpret_cast<Class##Private *>(d_myxxx_ptr); } \
inline const Class##Private* d_myxxx_func() const { return reinterpret_cast<const Class##Private *>(d_myxxx_ptr); } \
friend class Class##Private;
#define MYXXX_DISABLE_COPY(Class) \
Class(const Class &); \
Class &operator=(const Class &);
#define MYXXX_DECLARE_PUBLIC(Class) \
inline Class* q_myxxx_func() { return static_cast<Class *>(q_myxxx_ptr); } \
inline const Class* q_myxxx_func() const { return static_cast<const Class *>(q_myxxx_ptr); } \
friend class Class;
#define MYXXX_D(Class) Class##Private * const d = d_myxxx_func()
#define MYXXX_Q(Class) Class * const q = q_myxxx_func()


//
// Base classes
//

class MYXXXPrivateHolder;

class MYXXXPrivate
{
protected:
MYXXXPrivate(MYXXXPrivateHolder *p) : q_myxxx_ptr(p) {}
MYXXXPrivateHolder *q_myxxx_ptr;
public:
virtual ~MYXXXPrivate() {qWarning() << Q_FUNC_INFO;} // this is the line!
};

class MYXXXPrivateHolder
{
protected:
MYXXXPrivateHolder(MYXXXPrivate *p)
: d_myxxx_ptr(p) {}
~MYXXXPrivateHolder() {
delete d_myxxx_ptr;
}
MYXXXPrivate *d_myxxx_ptr;
};


//
// Use case
//

class FilePrivate;

class File : public QObject, public MYXXXPrivateHolder
{
Q_OBJECT
public:
File(QObject *parent = 0);
~File() {
qWarning() << Q_FUNC_INFO;
}
private:
MYXXX_DECLARE_PRIVATE(File)
MYXXX_DISABLE_COPY(File)
};


class FilePrivate : public MYXXXPrivate
{
public:
FilePrivate(File *_file) : MYXXXPrivate(_file) {}
~FilePrivate() {
qWarning() << Q_FUNC_INFO;
}
private:
MYXXX_DECLARE_PUBLIC(File)
};

#endif

#include "foo.h"

File::File(QObject *parent)
: QObject(parent)
, MYXXXPrivateHolder(new FilePrivate(this)) {}

#include "foo.h"

int main(int argc, char *argv[])
{
File f;
return 0;
}
Well, that compiles and gives the expected result:

virtual File::~File()
virtual FilePrivate::~FilePrivate()
virtual MYXXXPrivate::~MYXXXPrivate()

But when I change the marked line to

virtual ~MYXXXPrivate() = 0;

I get the compilation error

foo.o: In function `FilePrivate::~FilePrivate()':
make: Leaving directory `/data/dev/asp/testprojekt-build-desktop'
foo.cpp:(.text._ZN11FilePrivateD2Ev[_ZN11FilePrivateD5Ev]+0xeb): undefined reference to `MYXXXPrivate::~MYXXXPrivate()'
foo.cpp:(.text._ZN11FilePrivateD2Ev[_ZN11FilePrivateD5Ev]+0x1c7): undefined reference to `MYXXXPrivate::~MYXXXPrivate()'
foo.o: In function `FilePrivate::~FilePrivate()':
foo.cpp:(.text._ZN11FilePrivateD0Ev[_ZN11FilePrivateD5Ev]+0xeb): undefined reference to `MYXXXPrivate::~MYXXXPrivate()'
foo.cpp:(.text._ZN11FilePrivateD0Ev[_ZN11FilePrivateD5Ev]+0x1d7): undefined reference to `MYXXXPrivate::~MYXXXPrivate()'
collect2: ld returned 1 exit status
make: *** [testprojekt] Error 1


What do I not see???


Thanks

ChrisW67
5th April 2012, 09:53
Virtual destructors must always be implemented even if they are declared pure virtual. By making the MYXXXPrivate virtual destructor pure you are saying that this class is designed to be inherited (virtual) and must be inherited (pure). Using the destructor this way is fine but any derived class will call the abstract class destructor (statically bound) when it is deleted, which is why an implementation is required. The destructor implementation can be empty but will have to be outside the declaration

Lykurg
5th April 2012, 10:16
Hmm, ok, and then why someone would like to do that? (Initial I planned to force derived classes to define/declare their own destructors...)

I can't the any difference between
/* *.h */
virtual ~XXX() = 0;
/* *.cpp */
XXX::~XXX() {}and
virtual ~XXX {}beside the hiding of the implementation, but normal you also don't make the base class of your private classes public...

ChrisW67
5th April 2012, 12:27
The difference between your two options is that the first prohibits creation of a concrete instance of class XXX (pure virtual), while the second does not.

To make MYXXXPrivate abstract it has to have at least one pure virtual member function. If the class has no other methods then the only method you can make pure virtual is the destructor (constructors cannot be virtual). The destructor of the base class will be called as part of the destruction of the derived class, which is unlike other member functions in the base class that are only called if the derived class explicitly calls then. You cannot suppress the call to the base class destructor so you must provide an implementation to satisfy the linker.

Lykurg
5th April 2012, 12:38
Doh!


pure virtual

I hate that. If I am too focused on one particular problem I loose the bigger picture...

Thanks for taking me by the hand.