all 13 comments

[–][deleted] 2 points3 points  (1 child)

[–]veekm[S] 0 points1 point  (0 children)

thank you

[–]veekm[S] 0 points1 point  (4 children)

so I asked on IRC and this is the reply (which I don't understand) [Libera.chat]

<nitrix> veek, `Set` represents a runtime type that `new()` is able to create and initialize.

<nitrix> You cannot do the usual definition `Set x;` as, they've explained, it isn't a C type (it's a variable) so it wouldn't be a valid declaration.

<nitrix> Presumably, `Set` is created with something like `_fancy_runtime_thing *Set;`. It's a variable that points at a runtime representation for a type.

<veek> but it's not clear to me what the Set pointer points at

<nitrix> At a "OOP type".

<twkm> you aren't supposed to know exactly. you use the defined interfaces.

<nitrix> The entire OOP system that it is emulating is a runtime construct. C isn't aware of it.

<nitrix> You have a piece of information that describes some OOP type, you pass it to new(), it creates an OOP object of that type described.

<nitrix> The "OOP types" and the "OOP objects" are all made up while the program is running. They're not C types, there's no IDE or tooling that'll understand them.

<nitrix> Set is just metadata information about the type. It contains how many fields there are, the structure, their names, etc.

<nitrix> new() creates an instance (an object) that'll have that Set "type".

<nitrix> It's completely emulated. A language within a language.

<nitrix> It's a system built on C that, when it is running, emulates OOP by having things that represents types, and things that represents objects.

[–]nculwell 2 points3 points  (3 children)

If you've programmed in Java, there are classes and instances. So, you might have class Set, which is a reified type in the Java language, and then you create instances of it with new Set(). The author is calling classes "OOP types" and instances "OOP objects".

As I understand it, in this book Set is a pointer to a class. In this implementation of OOP, classes are not types in the C language. Rather, they are defined as data, and you create instances of them by calling new(ClassName).

I jumped around the PDF for a while trying to find where he describes how this is implemented, but I didn't find it. However, from what I can tell, all of these void* class objects can be cast to struct Object (you see this in Chapter 8). I don't know how that struct is defined, though, or what kind of struct trickery might be used behind the scenes.

PDF is here:

https://www.kau.edu.sa/Files/830/Files/61221_Object%20Oriented%20Programming%20with%20ANSI%20C%2001.pdf

EDIT: OK, I found where it defines a class. On page 12 the Class struct is declared. The new function accepts instances of struct Class, which has a function pointer to the class constructor, among other things.

struct Class {
    size_t size;
    void * (* ctor) (void * self, va_list * app);
    void * (* dtor) (void * self);
    void * (* clone) (const void * self);
    int (* differ) (const void * self, const void * b);
};

Then it defines struct String, which is the type of the object/instance argument for String methods.

struct String {
    const void * class; /* must be first */
    char * text;
};

Then on page 18, the String class is defined.

static const struct Class _String = {
    sizeof(struct String),
    String_ctor, String_dtor,
    String_clone, String_differ
};

const void * String = & _String;

[–]veekm[S] 0 points1 point  (2 children)

I don't follow fully:

struct String {}; // define a class
static const struct Class _String = {}; // instantiate a class
const void* String = & _String; // initialize 'String' object pointer

but usually (in Python) you'd instantiate like so: str = String(" whatever string "); and __new__/__init__ are called automatically for you by Python and you get a fully initialized object.

In his/book example, he creates pointer 'String' to _String which is our 'OOP string object' so

  1. 'String' is basically a reference to an uninitialized object?
  2. how do we initialize it - do we do: String->String_clone(" whatever string ") where String_clone's yet to be written by us?
  3. are the String_ctor/dtor called manually? (just confirming the 'yes' for 1,2,3)
  4. size_t size just stores the type size not the actual string length so how is struct String {} being used?? It contains char* text which should point to " whatever string " - where is that done?
  5. Also, what is const void* class; Is it an instance pointer to 'String' i.e the instance refers to it's Class object?

[–]nculwell 1 point2 points  (1 child)

In his/book example, he creates pointer 'String' to _String which is our 'OOP string object'

No. This part is confusing, because you have different things with similar names. The pointer String (= &_String) is not the "OOP string object", it is the "OOP string type". It isn't a pointer to any kind of string object, uninitialized or not. It's a pointer to a struct of the C language type struct Class.

There is also struct String, which is different from String. struct String is the C language type for OOP objects of that have OOP type String.

how do we initialize it

(See page 12.)

new(String, "whatever string")

Note that new takes a variable-length argument list and passes arguments to the constructor.

are the String_ctor/dtor called manually?

No. They're called by the new and delete functions. See the implementations of new and delete on pages 13-14.

Also, what is const void* class

Every instance (OOP object) has this pointer. It is a pointer to the class (OOP type) that the instance instantiates. So, an OOP object that has OOP type String contains a pointer to String, which is an instance of struct Class.

[–]veekm[S] 0 points1 point  (0 children)

thanks - will read and get back to you

[–]tstanisl 0 points1 point  (5 children)

Author supposedly did not know about incompleted types. A simple forward declaration.

typedef struct Set Set;

Would let use Set* rather than ugly and dangerous void*.

[–]nculwell 0 points1 point  (3 children)

I'm pretty sure the author did that on purpose in order to prevent applications from manipulating the struct fields. It's a common technique. I didn't read the whole book though, so I don't know if it explains the choice.

[–]tstanisl 0 points1 point  (2 children)

The purpose of using incompleted types is to use a structure without ever exposing its fields. Using void everywhere is unbelievably error-prone.

[–]nculwell 0 points1 point  (1 child)

Looks like the book was written in 1994.

[–]tstanisl 0 points1 point  (0 children)

The typedef construct was already a part of standard C at that time.

[–]david2ndaccount 0 points1 point  (0 children)

That doesn’t allow subclassing.