Yeah, I believe it. But when push comes to shove, what I saw looked
and smelt more like bugs than complexity. I saw member functions being
called before the constructor was called, and learned you can't rely
on constructor initialization order for global objects.
(This is probably the wrong newsgroup to gripe about such things,
but as far as I am concerned, this ordering problem is just the
tip of the iceberg.)
So I turned them into pointers instead, and discovered that C++ will
call member functions for objects that DON'T EVEN EXIST. They can be
NULL, and you can still call their functions. The function gets a NULL
'this' pointer!
Yes.
The following code compiles and runs, and A() executes. Apparently the
object orientation of C++ is a thin scum over procedural programming ...
Actually, it is a fairly thick layer. But for simple cases like
this one, there is a direct conversion from C++ to C:
class My
{
public:
void A()
{
if (this==NULL) printf("NULL Pointer ");
}
};
(You need to #include at least one header, and the printf here
should really be "std::cout <<", etc. But never mind that.) Here
the "class function" A() that is a member of the data structure
called "My" is obtained simply by gluing together the type ("My")
and the function name ("A"), which you can do manually in C by
spelling it My_A(). Of course, you give up the automatic "if the
type changes, the function name changes too" part that C++ gives
you.
void main()
{
My *ptr=NULL;
ptr->A();
}
This needs to be "int main()" just as in C.
In any case, the C++ compiler here just glues together (in a
more error-resistant way) the name "My" and the name "A", so
this is like writing the C code:
struct My { char dummy; };
/* dummy element required only because C forbids empty struct */
void My_A(struct My *this) {
if (this == NULL)
printf("NULL pointer\n");
}
int main(void) {
struct My *ptr = NULL;
My_A(ptr);
}
If you change member function A to "virtual", it stops working.
This is because a "virtual" member function is not built by name
-- i.e., we no longer just say "well gosh, `ptr' has type `My' so
we just call My_A() and supply `ptr' as a secret argument for the
`this' parameter". Instead, there is now a "virtual function table"
attached to the data type.
This is where C++ actually gives up something you can do manually
in C. For various (good and bad) reasons, the virtual function
table is essentially an operations vector, and the data structures
-- objects of type "My" -- point to the table. So in C we might
now write:
struct ops_for_My {
void (*A)(struct My *this);
};
static struct ops_for_My ops_for_My = { A };
struct My {
struct ops_for_My *vtable;
/* other data elements go here as needed */
};
When you create an actual object of type "My", the compiler will
fill in the "vtable pointer":
/* C code loose-equivalent of the C++ stuff */
ptr = ...;
ptr->vtable = &ops_for_My;
A later call of the form:
ptr->A(); // in C++
translates to the C call:
ptr->vtable->A(ptr);
and if "ptr->vtable" points to the default table, that calls the
default "My_A()" function. (A derived class that overrides the
default My_A() function just requires another ops table, with a
different "A" pointer.)
Although the need is somewhat specialized and occasional, note that
if you have written this in C, you now have a name for the vtable(s)
that implement the "virtual functions" for anything that is, or is
derived from, a "My". Clearly, if ptr==NULL, this:
ptr->vtable->A(ptr);
is not going to work -- but in C, we can do this instead, in
those rare cases where we want to:
ops_for_My.A(NULL);
In C++ one has to resort to any number of workarounds -- not that
they are particularly horrible or anything; but it is annoying to
know that there is a virtual function table all set up, yet you
are not allowed to access it. You *must* have an instance of the
class in order to access that class's virtual function table.
(The easiest workaround is thus to have a static "dummy" instance.)
In any case, both regular "class functions" and "virtual functions"
are easy to do in C, using these techniques. C++ merely automates
some of the drudge-work, avoiding the opportunity to get it wrong,
but also requiring you to give up some control over it.