I have yet another question about multiple inheritance design which has an answer i.e. here (but focused on footprint) or here (too vague), but most answers I stumbled upon is emphasizing the performance drawbacks. However (as Bjarne Stroustrup claims here) it is a language feature which should be prefered to workarounds. Here is a longer example to ilustrate the question which follows the example:
The Example
In Czech Republic, the birth number (SSN equivalent) is assigned in this format: YYMMDDXXX, so let's have a class to get the birth date in standard D.M.YYYY:
class Human {
protected:
char output[11];
char input[10];
public:
Human (const char* number) {
strncpy(input, number, 10);
if(!number[10]) throw E_INVALID_NUMBER;
}
static int twoCharsToNum(const char* str) {
if(!isdigit(str[0]) || !isdigit(str[1])) throw E_INVALID_NUMBER;
return (str[0]-'0')*10 +str[1]-'0';
}
const char* getDate() {
sprintf(output, "%d.%d.%d", getDay(), getMonth(), getYear());
return output;
}
// range check omitted here to make code short
virtual int getDay() { return twoCharsToNum(input+4); }
virtual int getMonth() { return twoCharsToNum(input+2); }
virtual int getYear() { return twoCharsToNum(input)+1900; }
};
Three methods are virtual, because females got +50 to their month. So let's inherit Man and Woman classes to get the date properly:
class Man : public Human {
public:
using Human::Human;
};
class Woman : public Human {
public:
using Human::Human;
int getMonth() {
int result = twoCharsToNum(input+2)-50;
if(result<0) throw E_INVALID_GENDER;
if(result==0 || result>12) throw E_INVALID_RANGE;
return result;
}
};
Since 1954 the number has 4 digits appendix, not 3 (there is a sad story behind this mentioned at the end of this question). If the library was written in 1944, ten years later somebody can write a Facade to get the birth date correctly for future millenials:
class Human2 : public Human {
public:
using Human::Human;
virtual int getYear() {
int year = twoCharsToNum(input);
if(year<54 && strlen(number)==10) year+= 2000;
else year+= 1900;
return year;
}
};
class Man2 : public Human2 {
public:
using Human2::Human2;
};
In class Woman2
we need the Woman::getMonth
method, so we need to solve the diamond problem:
class Human2 : virtual public Human { ... };
class Woman : virtual public Human { ... }; // here is the real issue
class Woman2 : public Human2, public Woman {
using Human2::Human2;
using Woman::Woman;
};
The Diamond problem diagram:
Woman2
^ ^
| |
Woman Human2
^ ^
| |
Human
The Question
The issue is that Human
, Man
and Woman
could be in a form of binary library where the client code can not rewrite the inheritance to virtual. So how to properly design extensible library to enable multiple inheritance? Should I make every inheritance in the library scope virtual (since I don't know in advance how it could be extended), or is there any more elegant universal design?
Regarding the performance: isn't this the domain of low-level programming and compiler optimization, shouldn't the design perspective prevail on high level programming? Why compilers don't auto virtualize inheritance like they do in RVO or inline
calls decisions?
The sad story behind the example
In 1954 some technically inspired bureucrat decided that tenth cipher will be added in a way so the number will be divisible by 11. Later the genius figured out that there are numbers which can not be modified this way. So he issued an exception that in these cases the last number will be zero. Later that year an internal directive was issued, that no such exceptions will be allowed. But in the meantime some 1000+ birth numbers were issued which are not divisible by 11, but still legal. Regardless of this mess, the century of the year can be inferred by the number length until 2054, when we will experience Y2K revival. Alas, there is also a common practice that immigrants born before 1964 get assigned a 10-digit birth number.