C++ classes, inheritance and portability

Discussions on more advanced topics such as monolithic vs micro-kernels, transactional memory models, and paging vs segmentation should go here. Use this forum to expand and improve the wiki!
User avatar
xenos
Member
Member
Posts: 1123
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

C++ classes, inheritance and portability

Post by xenos »

My kernel is undergoing a major redesign and I'd like to hear some opinions and suggestions. Currently, I'm using C++ inheritance to derive architecture dependent, specialized classes from generic, architecture independent base classes. To give you an example, here is the basic design pattern used to implement my console driver:

Console.h

Code: Select all

class Console
{
protected:
	void putchar(char c) = 0; // Architecture dependent putchar function, must be implemented by each console.

public:
	void WriteFormat(const char* format, ...); // Similar to printf.
};

// Location where the Console object will be placed in memory.
extern char console_space[];

// Returns a reference to the Console object.
inline Console& console(void) { return(reinterpret_cast<Console&>(console_space)); }
Console.cpp

Code: Select all

#include <Console.h>

void Console::WriteFormat(const char* format, ...)
{
	// Find out which characters to print and call putchar() to put them on the screen.
}
arch/x86/VGAConsole.h

Code: Select all

#include <Console.h>

class VGAConsole : public Console
{
private:
	char cursor_x, cursor_y;

protected:
	void putchar(char c);
};
arch/x86/VGAConsole.cpp

Code: Select all

#include <arch/x86/VGAConsole.h>

void VGAConsole::putchar(char c)
{
	// Put character on the screen
}

char console_space[sizeof(VGAConsole)];
arch/x86/Init.cpp

Code: Select all

#include <arch/x86/VGAConsole.h>

void Init(void)
{
	// Use placement new to create Console object at reserved space.
	new (console_space) VGAConsole();
	// Write something.
	console().WriteFormat("Console started!\n");
}
This design pattern has the following advantages:
  • It is easily portable. To implement a new console type, one simply needs to derive a new class from Console and implement putchar (and other architecture dependent functions which I omitted in my code example). It can simply be used through the console() wrapper function.
  • There is no need to store a pointer to the console object. The compiler transforms the inlined console() function to a reference to console_space, and the linker inserts the correct address.
But it also has some drawbacks, which is the reason why I plan to rewrite everything:
  • Whenever a console function is called, the location of the console_space is passed as an argument - even though this is a constant value, i.e. it is the same for every call since there is only one instance of the Console class in the kernel.
  • Every access to member variables of the console object is indirected through the "this" pointer - which always points to the same, unique Console instance.
  • Every call to an architecture dependent console function is indirected through the vtable of the corresponding architecture dependent derived class. Again, this is unnecessary, since exactly one implementation of every function such as putchar is compiled into the kernel.
  • GCC's "strict aliasing" rules are broken since an object of type char[] is referenced through a pointer of type Console&.
The goal of my redesign is to get rid of these drawbacks, while still keeping the portability of this design pattern. Do you have any ideas or suggestions? I've been spinning my head around this for a while, but without a final conclusion.
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
FlashBurn
Member
Member
Posts: 313
Joined: Fri Oct 20, 2006 10:14 am

Re: C++ classes, inheritance and portability

Post by FlashBurn »

As I´m also just rewriting (better porting) my kernel to C++ and I´m new to C++, I´m not sure if this it what you need/want. But why don´t you use static functions for all things which only exist one time?
User avatar
xenos
Member
Member
Posts: 1123
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

Re: C++ classes, inheritance and portability

Post by xenos »

Simply using static functions.was one of the things I thought about. The problem is the following:

Suppose all functions from my example would be static. The first question is: Where would I put architecture dependent member variables such as cursor_x, cursor_y? (A different console, for example, a serial terminal, does not have a cursor - but maybe a serial port number and baud rate instead.) Obviously I cannot put them in Console, so they must be members of VGAConsole. Of course they must be static, because otherwise I cannot access them from a static function (without an instance of the VGAConsole class).

Now suppose that some architecture independent part of my kernel wants to print something on the screen, so it needs to call WriteFormat. Of course I cannot call any static member functions of VGAConsole from this part of my kernel, since it should not depend on the architecture, and thus not on the type of console the system uses. So WriteFormat must be a static member of class Console.

The function Console::WriteFormat decomposes the text and needs to call some architecture dependent function to print the characters to the screen (which I called putchar in my example). So where do I have to put this putchar function? If I put it into class Console(), I cannot access the member variables of class VGAConsole. So obviously putchar must be a static member of VGAConsole. But in that case I cannot call it from Console::WriteFormat, because this function does not know whether I have a VGA or some other kind of console.

In my example I used virtual functions to call a member function of the derived class - but that does not work if the functions are static...

The template specialization approach looks quite interesting to me - I'll have a look and try to figure out how I could implement it in my kernel. Removing all this virtual function overhead would be great :) And I totally agree, it would probably be cleaner. And yes, the singleton pattern would be cleaner, too... The reason why I did not use it so far is that I'm not quite sure how it works together with class inheritance. But I guess I can figure that out :)

And I should definitely profile my code ;)
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
User avatar
xenos
Member
Member
Posts: 1123
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

Re: C++ classes, inheritance and portability

Post by xenos »

berkus wrote:static is a bad idea here. You'd be trying to mimic or write assembly in C++ and that doesn't pay off in the long run.
That's another reason why I soon disregarded that idea ;)
berkus wrote:This is not what I said. I said to not optimize prematurely. You can always profile it later when there's actually something to profile. Right now you simply can focus on getting architecture and algorithms right, not squeezing every last bit of performance out of them.
Yes, that's what i meant - I should profile my code before I start optimizing it, but of course after writing a clean implementation of the algorithms I need.
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
User avatar
xenos
Member
Member
Posts: 1123
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

Re: C++ classes, inheritance and portability

Post by xenos »

I'm still spinning my head around the template approach and I can't quite figure out how to implement something like the console class from my example using template specialisation. So basically I need something like the following:
  • A single "base class" / "template" / "interface", which provides the high level console functions such as WriteFormat, which can be called from anywhere in the kernel.
  • One "derived class" / "template instantiation" / "interface implementation" for each architecture, which defines architecture dependent data (such as cursor positions) and provides the low level functions such as putchar, which are called by the high level functions.
  • A single instance of the architecture dependent console class, which holds the architecture dependent data.
Suppose I write some template like the following:

Code: Select all

template <class T> class Console {...};
Which class / type would I have to pass as template parameter T if I instantiate some architecture dependent console class, similar the the VGAConsole class from my example? Some struct containing the architecture dependent data for this console type? Further suppose that I created a single instance of some architecture dependent console class - how would I access this object from the architecture independent class template? In other words, how would some high level function such as WriteFormat know which instantiation of a low level function such as putchar it should call?

Sorry for these "simple" questions - i guess I'm just a bit blind right now ;)
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
FlashBurn
Member
Member
Posts: 313
Joined: Fri Oct 20, 2006 10:14 am

Re: C++ classes, inheritance and portability

Post by FlashBurn »

berkus wrote: static is a bad idea here. You'd be trying to mimic or write assembly in C++ and that doesn't pay off in the long run.
Ok, explain why I´m mimicking assembly and why it doesn´t pay off in the long run.

Maybe it´s not the best for this problem, but for the problem where I use it, it was (to my knowledge) the only good solution.

The advantage is (when you only have one instance of a class) that the compiler will always use direct addresses and doesn´t use a pointer to an object and you only need to define the "interface" (methods of a class) not the member variables.
User avatar
xenos
Member
Member
Posts: 1123
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

Re: C++ classes, inheritance and portability

Post by xenos »

Thanks berkus, I thing I got the point :) I'll play around a bit with this design to figure out its pros and cons before I implement it in my kernel...
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
User avatar
neon
Member
Member
Posts: 1568
Joined: Sun Feb 18, 2007 7:28 pm
Contact:

Re: C++ classes, inheritance and portability

Post by neon »

Hello,

The only issue I particularly dont like about this design is why is the console inside of the kernel in the first place? I suppose its ok for a monolithic design, of course, its just a personal dislike.
It is easily portable. To implement a new console type, one simply needs to derive a new class from Console and implement putchar
This is incorrect. It just means its extensible, has nothing to do with portability. The design does allow for good extensibility at the cost of always having to implement driver functionality into the kernel itself. Yuck.

Just my 2 cents, these might not be concerns either but should be taken into consideration on your design.
OS Development Series | Wiki | os | ncc
char c[2]={"\x90\xC3"};int main(){void(*f)()=(void(__cdecl*)(void))(void*)&c;f();}
User avatar
xenos
Member
Member
Posts: 1123
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

Re: C++ classes, inheritance and portability

Post by xenos »

@neon: I agree with you - and I should have been more precise in my first post. The console class I described in my first post is used only in the very first boot phase, before any drivers are loaded. It is discarded as soon as a video driver is loaded, which then provides the console functions through an IPC mechanism.

By "easily portable" I meant that one can easily write an "early boot" console for a different architecture and reuse all the architecture independent code. The only thing that needs to be rewritten is the implementation of the low level functions, such as putchar. For example, there is an incomplete Atari port of my kernel, where the console class needs to print characters pixel by pixel, since there is no such thing as a text mode. For this port I simply derived a new class named AtariConsole from Console, and create an instance of this class in the Atari startup code.
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
FlashBurn
Member
Member
Posts: 313
Joined: Fri Oct 20, 2006 10:14 am

Re: C++ classes, inheritance and portability

Post by FlashBurn »

@berkus

Maybe I can also use this thread for my "problem".

I have an interface class like:

Code: Select all

class Pmm {
 void* alloc();
 void dealloc(void* addr)
};
So my whole kernel code will use an object of the type "Pmm" for allocating and deallocating memory. The problem is now that I don´t know at compile time which implementation of Pmm the kernel will use.

So the only real solution I came up with, was static methods and static global variables. So I can implement more than one version of Pmm, compile them and link them at runtime. Also as I said, this way the compiler wont use a this-pointer, which is faster and it´s not needed when you only will have one instance (over the whole runtime) of an object-type.
FlashBurn
Member
Member
Posts: 313
Joined: Fri Oct 20, 2006 10:14 am

Re: C++ classes, inheritance and portability

Post by FlashBurn »

Maybe run-time is the wrong word (and I don´t wont to use a pointer if I can have the addresses!).

My bootloader does some hardware-checks and decides which variant of Pmm to link into the kernel. So I can use the addresses of the static methods and I don´t have a this-pointer/pointer to an object.

I see the advantages of oo-programming, but when I have methods which don´t need a this-pointer then they become static (so this is also right for classes which only have one instance).
FlashBurn
Member
Member
Posts: 313
Joined: Fri Oct 20, 2006 10:14 am

Re: C++ classes, inheritance and portability

Post by FlashBurn »

If I´m right it´s the same as using static with classes? So it´s only a cosmetic problem?!
User avatar
xenos
Member
Member
Posts: 1123
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

Re: C++ classes, inheritance and portability

Post by xenos »

This "namespace approach" was another thing I was thinking about in order to get rid of passing a "this" pointer to a constant location whenever a member function is used. However, it didn't appear very clean to me, since it mainly circumvents the object oriented nature of C++ and drops back to a plain C programming style.
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
FlashBurn
Member
Member
Posts: 313
Joined: Fri Oct 20, 2006 10:14 am

Re: C++ classes, inheritance and portability

Post by FlashBurn »

XenOS wrote: However, it didn't appear very clean to me, since it mainly circumvents the object oriented nature of C++ and drops back to a plain C programming style.
Programming in C++ doesn´t mean one has to use always object orientation! What´s the problem with C programming style if it fits and is faster?
User avatar
xenos
Member
Member
Posts: 1123
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

Re: C++ classes, inheritance and portability

Post by xenos »

I didn't say that there is a problem using C style coding in C++ - but the question is, if one uses C style coding anyway, why not use C from the beginning instead of C++?
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
Post Reply