Introduction
You can add only few lines of code into your top header to be able to write C++ classes with better semantic.
#define virtovr virtual
#define virtnew virtual
#define virtabs virtual
#define virtimp virtual
#define virtrep virtual
#define virtadd virtual
#define virtins virtual
#define virtwrp virtual
Consider this as suggestion to improve C++ language. Such extention will not force developers to use it, but if somebody want he can start use it. Without compiler support we still can use this in our code, but there is no error messages.
Background
C++11 override
Many years we have live with 'virtual' keyword in C++ until its improvement in C++11 with additional 'override' specifier. This was the first push to think about the subject.
I never liked the idea to put this speciifer at the end of function declaration, because if you have functions with parameters it becomes more and more hidden. Eye of developer should jump more over the code to find and recognize it.
virtual void foo() const override;
virtual void SomeGoodName(
const char* inParam1,
long long int inParam2,
std::vector inParam3 ) const override;
I would prefer to have this information first in the line:
virtovr void foo() const;
virtovr void SomeGoodName(
const char* inParam1,
long long int inParam2,
std::vector inParam3 ) const;
virtovr - is combination of 'virtual override'.
I have notice that virtual, inline, static keywords, which can go before a function declaration, have length 6-7 chars. This is why all new "keywords" have the same length.
As for me, such keyword is more readable, short and consistent in the text of C++ code, comparing to 'override' at the end of function declaration.
Next idea - virtnew
In our projects we have C++ classes that not only override a parent virtual method, but add more.
If to think about what happens on the low level, then it is easy to see that such virtual method allocates a new position in the virtual table of the C++ class.
class A
{
virtual ~A();
virtual void f1();
virtual void f2();
};
class B : public A
{
virtual ~B();
virtovr void f1();
virtovr void f2();
virtual void f3();
};
- Virtual table of class A contains 3 items - pointers to class methods.
- Virtual table of class B contains 4 items. Virtual method f3() introduced in this class needs one more.
We can consider this as fundamental difference between overriden virtual methods f1, f2 and ... a new virtual method f3.
This bring us to idea of 'virtnew' keyword.
class B : public A
{
virtual ~B();
virtovr void f1();
virtovr void f2();
virtnew void f3();
};
Advantages:
* Developer _reading_ 'virtnew' immediately understands that the parent class do NOT contain this method.
* Compiller and smart IDE can check this fact providing error messages similar to 'override' in C++11.
Yes of course, without support of this by compiller, it looks to be a very weak idea. But during last couple of years, me personally have used this in the most complex classes of our project, and I have found this to be helpful.
virtins, virtadd, virtwrp, virtrep
As I have said, we was using this virtnew and virtovr keywords last few years. Few months ago, I have recall some article with some critic of C++ features. One of that was: developer cannot specify how a virtual method can be implemented in a child class:
- call parent method at first and add some code: { inherited::foo(); code_after; }
- some code and call parent method at the end: { code_before; inherited::foo(); }
- call of parent method is wrapped by some code: { code_before; inherited::foo(); code_after; }
- replace parent method { alternative_code }
This have force me to look on existed code under a new point of view. So we have at least 4 different cases/patterns of a child OVERRIDEN virtual method. Then why we should to use the same keyword 'virtovr' for them all? May be there is sense to have more keywords?
class C : public B
{
virtual ~C();
virtrep void f1();
virtadd void f2();
virtins void f3();
virtwrp void f4();
};
What advantage of this?
* Developer reads virtrep - 'virtual replace', and understands that class C overrides f1() from a parent clas. Becides, method is implemented as REPLACEMENT to parent algorithm. Developer understands that if go to C::f1() he will not see there call to B::f1().
IMPORTANT: developer understands this reading only ONE word of the text.
* virtadd - 'virtual adds' - overriden method at first do calls the parent method later do more code.
* virtins - 'virtual insert' - overriden method at first do some new own code, later do calls the parent method.
* virtwrp - 'virtual wrap' - overriden method do calls the parent method, but wrapps it before and after with some additional code.
* virtrep - 'virtual replace' - overriden method do not calls the parent method, but provides alternative algorithm.
OBSERVATION:
- Both "virtual ... override" and "virtovr" expose the fact that method is overriden.
- virtins, virtadd, virtwrp, virtrep - additionally expose how it was overriden.
Smart IDE, in ideal, should monitor the code of functions to recognise a pattern of code, and prompt to developer or even automatically correct class declaration.
virtimp
Later in discussion with developers, one of them have recognized one more case/pattern of overriden virtual method - this is the first implementation of abstract parent method.
class A
{
virtnew void foo() = 0;
};
class B : public class A
{
virtimp void foo();
};
Keyword 'virtimp' says us that this is a overriden virtual method of a parent class, which was abstract in the parent class, and this is its first implemenation in the hierarchy chain.
Compiller/IDE can check this fact to provide error messages.
virtabs
Finally, 'virtabs' keywrod - 'virtual abstract', can be replacement of =0 syntax. But for compatibility still allows it. All examples below are valid. Notice that an abstract method is always a virtnew method at least.
class A
{
virtnew void foo() = 0;
virtabs void foo(); virtabs void foo() = 0; };
Destructors
Desctructors do not call directly parent destructors. So virtadd, virtins, virtwrp will not have sense for them.
We can use may be only virtovr or virtrep keywords for destructors. But taking into account that destructor are called one by one, we cannot say that they replace each other.
This brings us to point that we should use virtovr only.
class A
{
virtnew ~A();
};
class B : public A
{
virtovr ~B();
}
Examples
This is a real life example of one of our classes as is:
class ValueSorter_Indirect : public ValueSorter_InPlace
{
typedef ValueSorter_InPlace inherited;
public:
ValueSorter_Indirect(
I_Index_Ptr inIndex,
uint64_t inRamToUse,
bool inSwapBytes );
virtovr ~ValueSorter_Indirect( void );
public:
virtadd void Init( void );
virtrep void FinalFlushToIndex( void );
protected:
virtins void AddImp(
I_Value_Ptr inValue,
REC_ID inRecID );
virtrep uint32_t CalcBuffSize( void ) const;
virtrep uint32_t CalcMaxPairs( void ) const;
virtrep void Sort( void ) const;
protected:
virtnew void ArrayOfPointers_Prepare( void );
virtnew void ArrayOfPointers_Cleanup( void );
protected:
char** mPointers; };
Lets analyse this code with the case when we have just 'virtual' and 'override'.
1) virtadd. Compare this line
virtadd void Init( void );
vs
virtual void Init( void ) override;
In both cases we understand that method Init()
- is virtual AND
- it presents in the parent class AND
- we override it here to change something.
You cannot get more information from the second line.
Once again. You cannot get more information because it not exists there.
But the first line gives me additionally:
- understanding how looks algorithm in .cpp file. It looks as { inherited::Init(); more_code; }
WOW! 3 magic chars virtADD allow to my brain to see picture of algorithm in OTHER file.
WOW! 3 magic chars virtADD allow to my brain to see DEPENDENCY to a parent class method.
Besides! The first line allows to me:
- less to type and less to read by eye. GOOD.
We have win-win solution! Isn't this a direction for C++11? Do more with less code.
2) virtrep. Few methods marked as 'virtual + replace = virtrep'. We understand that such method:
- totally REPLACEs the parent's algorithm.
- it not calls the parent algorightm.
3) virtnew. Two methods are marked as 'virtual + new'. So I understand right here, that
- parent class do not have such methods, they are introduced in the current class.
- Some method(s) of this class must call them to use polimorphism of this methods. In the example Sort() method calls
ArrayOfPointers_Prepare() and
ArrayOfPointers_Cleanup()
. - Some child-class can and, most probably, will override these virtnew methods, otherwise no sense to make them virtual.
Compare to C#
While I have write this article, I have note another one: Popular Misconception: The override that was not, where I have to see syntax for C#. They put override keyword at the start of function declaration.
class Derived : MyBase
{
public override string MethodA()
{
return "Hello from Derived::MethodA";
}
public new string MethodB()
{
return "Hello from Derived::MethodB";
}
}
In C++ language the 'override' was made as a specifier of function, similar to 'const', which by parser rules go at the end of function declaration.
But this is not a dogma. Developers of C++ parser can implement rules to have it as in C# if will be such wish.
Also note that C# allows new keyword. This sounds similar to 'virtnew'.
Final
Once again, yes it is clear that without compiller and IDE support it is easy to get "out-of date" keywords in the class declaration, which not correspond to the algorithm of virtual method.
Here we only expose idea ...
In the same time we try to use it in our many-years projects. This really saves time of understanding when developer reads code.
Ruslan Zasukhin is original developer of Valentina Database yet from 1993. He is co-founder of Paradigma Software, Inc. USA based company, which develops Valentina product line, including Valentina DB & Report Server, Valentina DB & Report ADK, Valentina Studio. http://www.valentina-db.com