Inheritance without virtual destructor
I have two classes that are used in a project. One class Callback
,, is responsible for storing information from the callback. Another class UserInfo
is information that is presented to the user. Basically, it UserInfo
should have been a very thin shell that reads data Callback
and transmits it to the user, and also provides additional material.
struct Callback {
int i;
float f;
};
struct UserInfo {
int i;
float f;
std::string thekicker;
void print();
UserInfo& operator=(const Callback&);
};
The problem is that adding members to Callback
requires identical changes to UserInfo
as well as updating operator=
and similarly dependent member functions. To sync them automatically, I want to do this instead:
struct Callback {
int i;
float f;
};
struct UserInfo : Callback{
std::string thekicker;
void print();
UserInfo& operator=(const Callback&);
};
UserInfo
Will now have all the same items as Callback
. In fact, the kicker is a data member thekicker
. There Callback
are no virtual destructors, and I believe other coders want it to stay that way (they strongly feel against the performance penalty for virtual destructors). However, there thekicker
will be a leak if the type UserInfo
is destroyed via Callback*
. It should be noted that it was not intended to UserInfo
ever be used through an interface Callback*
, which is why these classes were separate. On the other hand, having to change three or more pieces of code in the same way just to change one structure feels inelegant and error prone.
Question . Is it possible to allow UserInfo
inheritance in Callback
public (users should have access to all the same information), but disallow assignment of the reference to Callback
a UserInfo
, in particular due to the lack of a virtual destructor? I suspect this is not possible, since this is the main purpose for inheritance in the first place. My second question is, is there a way to keep these two classes in sync with each other using a different method? I wanted to make it a Callback
member UserInfo
instead of the parent class, but I want the data members to be directly read using user.i
instead user.call.i
.
I think I am asking for the impossibility, but I am constantly amazed at the witchcraft of stackoverflow answers, so I thought I would just ask to see if this is really a remedy for this.
source to share
You can always enforce the 'can not delete via base class pointer' constraint you mentioned (to some extent) by making the destructor protected in the base class:
i.e.
// Not deletable unless a derived class or friend is calling the dtor.
struct Callback {
int i;
float f;
protected:
~Callback() {}
};
// can delete objects of this type:
struct SimpleCallback : public Callback {};
struct UserInfo : public Callback {
std::string thekicker;
// ...
};
As mentioned, you can remove the assignment operator. For pre-C ++ 11, just make the unfinished prototype of this function private:
private:
UserInfo& operator=(const Callback&);
source to share
struct UserInfo : Callback {
...
// assignment from Callback disallowed
UserInfo& operator=(const Callback&) = delete;
...
};
Please note that STL has a lot of inheritance without a virtual destructor. The documentation explicitly states that these classes are not intended to be used as base classes.
some examples are vector <>, set <>, map <> ....
Another approach is to consider private inheritance when providing an accessor to expose the callback (in which case you can use encapsulation, which is cleaner).
source to share
Yes, there is a trickery you can use to keep items in sync and update operator=
automatically. This is ugly, although it includes macros and an unusual way of using the include file.
CallBackMembers.h:
MEMBER(int, i)
MEMBER(float, f)
In the other place:
struct Callback {
#define MEMBER(TYPE,NAME) TYPE NAME;
#include "CallbackMembers.h"
#undef MEMBER
};
struct UserInfo {
#define MEMBER(TYPE,NAME) TYPE NAME;
#include "CallbackMembers.h"
#undef MEMBER
std::string thekicker;
void print(); // you can use the macro trick here too
UserInfo& operator=(const Callback& rhs)
{
#define MEMBER(TYPE,NAME) NAME = rhs.NAME;
#include "CallbackMembers.h"
#undef MEMBER
return *this;
}
};
source to share
It is not possible to fulfill ALL the criteria you want.
Personally, I think your idea of ββmaking it a member and then using it user.call.i
is the best and most understandable option. Keep in mind that you are writing code that only uses this once, but you make up for it in maintainability (since yours UserData
should never change) and readability (since it is 100% transparent to end use which attribute is part of the callback data and which are auxiliary).
The only other option that might make sense is to use private inheritance instead, and the using
attribute or function in UserData
. That being said, you still have to add one using
when the new data is added to the callback, but you get the desired syntax user.i
for the clients.
source to share