But it all breaks down the moment you add a function that operates on the internals of both FILE and DIR, because this function doesn't belong to any one object. And if you don't notice it, you keep thinking FILE and DIR are objects, when in fact they're not (a method can't "belong" to two objects, at least as far as objects are perceived in the software world).
In short, I can agree that it's possible to write C with a OOP mindset. But (let's call it) imaginary OOP may not be actual OOP (as when enforced by the C++ compiler).
But yes, of course shoving everything under OO is bad. A bit of OO should be a mean to an end, not a inflexible design principle.
Ah, but now you're changing the problem :) we are not in C++, we do not have base classes, we are in C and have just two so-called objects. They don't belong to us, they're exposed as part of an API.
To recap, objects are entities that bind together data and functions. You can't have a single function belong to two objects, by definition.
Some people say that FILE and DIR plus the associated functions are objects; even though there's no syntax for it, they are objects conceptually, it's supposedly the same thing.
But if FILE and DIR are 2 conceptual/imaginary objects, to which of the objects does F(FILE, DIR) belong?
And, given they're not real objects, the answer can be whatever you want it to be.
You can say that in this scenario they stop being objects at all, or you can say FILE owns the function because it's first (but then about the encapsulation, why does F use the internals of both), or you can say conceptual objects can be conjoined like siamese twins, or whatever you feel like it. You can say the set of all functions that take an int is an object. Once you go down this road, you can say anything is OOP if you look at it in a certain light.
I understand that people can/do apply the OOP mindset to anything, it's their choice. But I don't think it's fair to characterize C as OOP since it doesn't even have syntax for objects. Syntax is real, people's mental models are not. But that's just me.
FILE is definitely OO object flavored: it is an opaque handle that encapsulates state and behavior through a generic runtime-dispatched interface. The biggest issue is that it is not open to user extension (except for the nonstandard fopencookie).
As an aside, you can of course have the moral equivalent of base classes in C. See for example the POSIX sockaddr class "hierarchy".
edit: to be clear, I'm in no way claiming that OO invented this stuff. I'm sure people where building the moral equivalent of vtables before Simula. OO just gave us a language to discuss this sort of features, which is useful. It also came with a lot of philosophical baggage and design guidelines (Everything is an object!), which IMHO are less so.
To the point where FILE is called an object, even though it's just an opaque pointer hiding the implementation. It can be phrased in OO terms but this irks me because it sounds as if OO invented it (I know you're not claiming this), and anything non-OO ("procedural, yuck") is bad. Which leads to the "everything must be an object" mindset that so many programmers exhibit today.
Which is why it bothers me to see "OOP with ANSI C". To me, those are just more or less normal patterns in C, why call it OOP when it's normal procedural code :) that's just me. Rant over.
It isn’t a thing in a C++ either if one makes all the fields public or if one uses friend.
Member accessibility is not essential to the idea of OOP, since there are OOP languages which don’t have it, or in which it is merely a convention, or something which can be overridden-and a person can use an OOP language which has it yet fail to use it.
So, I don’t think “C lacks private members” is a good argument against C being “OOP”. Especially when C actually has the moral equivalent of “private” given APIs can be based on opaque pointers, so your compilation unit has no idea what the fields or layout of some structure controlled by another is.
But it is a thing in OOP. That's the point. C++ is multiparadigm. All the OOP gurus will tell you that objects shouldn't expose data, only methods.
You can do the same in Smalltalk though-create a utility class containing a method which operates on one instance of each class, and create accessors to expose all their internals. If the ability to do that means C isn’t an OO language, then Smalltalk isn’t an OO language either. And if Smalltalk isn’t an OO language, then OO languages don’t exist.
Take the GNU C library. You don't own it, you just use it. It exposes some types (FILE, DIR), and some functions that use these types.
Then, some people look at this API and proclaim that FILE and DIR are (conceptually) objects, and that there's no difference to having 2 C++ classes, it's just syntactic sugar. And that, therefore, C is OOP (or at least the design of libc).
So now I ask - to which of the so-called objects does F(const char, FILE, DIR) belong? The answer is whatever you want it to be, because they're only objects in your mind, and you can imagine that F belongs to FILE, or to const char, or to all of them, or FILE/DIR stop being objects, and so on. Without syntax for objects (either explicit or implicit by convention), it's up to your imagination to decide what are the objects in your program. But if imagination is the criteria for OO-ness, then any programming language can be declared OO.
OO is such a fuzzy concept that almost every feature and "pillar" is optional except objects. My point is that if a language doesn't have a syntax for objects then it's not actually OO.
You can, of course, apply an OO mindset to your C program, but you can apply an OO mindset to anything Turing-complete, from Haskell to SQL and x86 assembly, so that's not saying much. If you approach everything with an OO mindset, that doesn't mean everything is OO.
Syntax is real, mindset is just in your mind.
You are arguing "C cannot be an OO language because it lets you do X, and an OO language won't let you do X." My counterargument is "Smalltalk lets you do X, but everyone agrees that Smalltalk is an OO language; hence, you claim that if a language lets you do X it can't be an OO language must be false".
> So now I ask - to which of the so-called objects does F(const char, FILE, DIR) belong? The answer is whatever you want it to be, because they're only objects in your mind, and you can imagine that F belongs to FILE, or to const char, or to all of them, or FILE/DIR stop being objects, and so on.
If a language permits methods/functions which do not belong to any class, does that mean it can't be OO?
A class-less method/function is equivalent to a single-method utility class. A language can't be OO if it lets you create a utility class?
> Without syntax for objects (either explicit or implicit by convention), it's up to your imagination to decide what are the objects in your program.
Consider this C code:
// Declarations in counter.h
typedef struct {
void *privateData;
int (*getValue)(Counter*);
void (*increment)(Counter*);
void (*decrement)(Counter*);
void (*destroy)(Counter**);
} Counter;
Counter* newCounter();
// In main function:
Counter* counter = newCounter();
printf("Initial value: %d\n", counter->getValue(counter));
counter->increment(counter);
printf("After increment: %d\n", counter->getValue(counter));
counter->decrement(counter);
printf("After decrement: %d\n", counter->getValue(counter));
counter->destroy(&counter);
assert(counter == NULL);
In the above program, why isn't `counter` an object? Assumably "privateData" is just a pointer to an int, but for all you know, the Counter value is actually stored as EBCDIC Roman numerals.> My point is that if a language doesn't have a syntax for objects then it's not actually OO.
C in itself has no concept of an "object". But, there are certain conventions you can adopt in C code which make something an "object". My code above is one example of such a convention, there are others – for example, Microsoft COM (an object is a struct whose first member is a pointer to a struct of function pointers, and the remainder of the struct is private to the implementing class).
The entire Objective-C runtime which is what powers the OO core of the language was also written with a similar purely C type system... It's just a small compiler layer on top...
> C in itself has no concept of an "object".
You answered your own question.
Without syntax to say "this is an object", you can take literally ANYTHING Turing-complete, mentally group data+functions and then claim "it looks like an object, therefore it is an object, why wouldn't it be an object". "Excel is OO", "Dwarf fortress is OO", "x86 assembly is OO", "pen and paper is OO".
Well, if everything is OO then nothing is OO, you've made it into a meaningless concept.
I suggest we stick to reality. The most basic requirement for a language to be OO is to have some explicit or implicit syntax for objects. That seems to reasonably partition languages into OO and non-OO.
> You are arguing "C cannot be an OO language because it lets you do X, and an OO language won't let you do X."
I'm arguing no such thing, and I never said such a thing. Would appreciate if you didn't put words in my mouth.
I'll ask again.
In my concrete example, given that FILE and DIR were classified as objects, to which of the so-called objects does F(const char*, FILE, DIR) belong? Possible answers: 1. FILE 2. DIR 3. None of them 4. Both of them 5. They stop being objects 6. New objects are created, and those own F 7.Something else
Pick one. I will then pick any other option and argue that, in my opinion, my chosen answer is the correct one. And then it's suddenly a matter of opinion rather than a matter of fact, you won't be able to prove I am wrong, just like I won't be able to prove I'm right. So much for imaginary objects.
How about this syntax?
#include "oo.h"
DEFINE_CLASS(Counter,
int METHOD(Counter,getValue,NO_ARGS);,
void METHOD(Counter,increment,NO_ARGS);,
void METHOD(Counter,decrement,NO_ARGS);,
void METHOD(Counter,multiplyThenAdd,(int multiplyBy, int add));
);
That's valid C, given appropriate preprocessor macro definitions.
It is a bit ugly, due to the limitations of the C preprocessor.
(Maybe someone else can do better than me.)
If C had a more powerful preprocessor, but was otherwise unchanged, the syntax could be a lot nicer.The DEFINE_CLASS() macro expands to declare a function:
Counter* newCounter(void);
Why isn't the return value of that function an object?Here is the contents of "oo.h":
#include "cloak.h"
#define JOIN(...) JOIN_IMPL(__VA_ARGS__,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,)
#define JOIN_IMPL(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,...) \
_1 _2 _3 _4 _5 _6 _7 _8 _9 _10 _11 _12 _13 _14 _15 _16 _17 _18 _19 _20 _21 _22 _23 _24 _25 _26 _27 _28 _29 _30 _31 _32 _33 _34 _35 _36 _37 _38 _39 _40 _41 _42 _43 _44 _45 _46 _47 _48 _49 _50 _51 _52 _53 _54 _55 _56 _57 _58 _59 _60 _61 _62 _63
#define DEFINE_CLASS(name,...) \
typedef struct name name; \
typedef struct name { \
void *_privateData; \
JOIN(__VA_ARGS__) \
void (*destroy)(name**); \
} name; \
name* new##name(void)
#define METHOD(class,method,args) \
(*method)(class* COMMA_IF(IS_PAREN(args)) WHEN(IS_PAREN(args))(EXPAND(EXPAND args)))
#define NO_ARGS
Where cloak.h is https://github.com/pfultz2/Cloak/blob/master/cloak.hThe above macros are admitttedly very hairy. If C had a better preprocessor, but was otherwise unchanged, they could look a lot nicer.
> I never said such a thing. Would appreciate if you didn't put words in my mouth.
A normal part of dialogue is, "I'm going to repeat your point back to you in my own words, and you can either agree with my restating of it, or point out at which point I've misunderstood you". That's what I was doing. "Would appreciate if you didn't put words in my mouth" is unnecessary hostility.
> In my concrete example, given that FILE and DIR were classified as objects, to which of the so-called objects does F(const char*, FILE, DIR) belong?
Take any language which you agree is "OO". Add one new feature (if it isn't already there): functions/methods which don't belong to any class/object. Now the function you are talking about is possible in that language. Did the language thereby suddenly cease to be OO when we added that feature? Most people would disagree with "Yes". But if "No", what is the actual difference between C and that language?
> Take any language which you agree is "OO". ...
A normal part of dialogue is answering the other person's questions. I asked the same question point blank several times, and you keep refusing to answer. Once you answer the question, then we can discuss your hypothetical scenarios about other languages. I believe I stated my question quite clearly.
> Why isn't the return value of that function an object?
> C in itself has no concept of an "object".
We already went over this.
I don't think your question necessarily has an answer, but if I have to pick, I'd say (3). F is a classless method; FILE and DIR are classes; as a classless method, F cannot belong to any class, so it can't belong to FILE or DIR.
If the function was F(FILE, const char*, DIR) then F might be a method of FILE instead, if the code base is following the "this is first argument" convention.
And why is that? Are objects and OOP not clearly defined? If you proclaim FILE and DIR to be classes, should it not be extremely clear what are their methods?
That's the problem right there, as I said it repeatedly. In C++, objects can be identified unambiguously.
Whereas in C, if you keep insisting it's OOP, the answer is whatever you want it to be. "FILE is an object, and I'll handwave away all inconvenient details that get in the way of my perception of FILE as an object". Sorry, I expect a higher level of rigour.
> F is a classless method
> FILE and DIR are classes
> If the function was F(FILE, const char, DIR) then F might be a method of FILE instead, if the code base is following the "this is first argument" convention.
The C standard library doesn't seem to follows such a convention. For example:
> fread(void , size_t, size_t, FILE );
> fgets(char, int, FILE );
> fseek(FILE , long int, int);
If FILE is a class, is fgets a method of this class? If fgets is a method of FILE, why is F in my example not a method of FILE too?
Finally! Somebody pointed this out.
OOP is merely a different way of structuring "Procedural/Imperative" programming. In the early days of OOP hype, the greats like Edsger Dijkstra, Niklaus Wirth were rather critical of it and denied that it was something very "Original". It was correctly pointed out as merely a technique for structuring code for "programming in the large". Once you understand this you can design and use OOP (for a specific definition) in any language.
Take the members of the C++ object and create a struct to hold the data then create a 'constructor' function for populating that data. Afterwards you just call the member functions of the original class, as simple C functions, and pass the struct as the first argument.
This in fact is what the actually happens at the assembly level when using a C++ compiler! The idea of an 'object' is purely an abstraction!
> This is not a thing in C. Linus Torvalds can add a function F that operates on the internals of both FILE and DIR at the same time, for example, and suddenly it's less clear who "owns" F and which one is the object.
In this respect C is strongly-typed and you will most definitely see compilation errors if `void F(FILE *fp)` is called as `DIR *dp; F(dp)`. If you want to make a function F that operates on both `FILE` and `DIR` types, you have to bypass the type checks explicitly! IOW, it is always clear to the reader of the code that F can take either a `FILE` or a `DIR`, or only a `FILE` or only a `DIR`. There is never any ambiguity to the reader.
OTOH, with C++ ...
> A C++ class binds a function to a specific struct, and the function can "belong" to no other struct.
Unlike C above, this is never clear to the reader of the code though. For example, looping over an array of instances calling `.F()` on each instance: the reader cannot know which implementation of `F` is being invoked.
It's literally impossible to tell just by looking at the loop. Sometimes it's impossible to tell even by examining all the code because the specific method that is bound to an instance may only be determined at runtime.
for (auto&& x: collection)
x.foo();
You say you have no idea what foo is.This is C:
for (int i = 0; i < collection_size; i++)
foo(collection[i]);
Now you say that which foo is being called is very obvious...... but [1]:
#define foo(x) x.foo(&x)
x.foo is a function pointer and someone has been implementing OO in C (far fetched? it is literally the title of the article). With C++ at least if I M-. on foo emacs will show me all possible targets (or directly take me to foo implementation if not polymorphic); With C, good luck teaching your IDE your custom flavor of OO [2].So, no, C and C++ are more similar than you think. The biggest difference is the presence of overloading which, in the static dispatch case, makes resolving the actual function from the function name slightly harder in C++ without tool help. Overloading has little to do with OO, although it is a form of static polymorphism.
In the dynamic case they are equivalent (well except for the fact that you have to build the vtables by hand in C and the syntax is a bit awkward.
[1] yes, yes, arguments are not protected against multiple evaluation. Sue me.
[2] with emacs of course this is actually possible, just merely hard.
My point is that C is not an OOPL. There's two aspects of OOP: 1) the concrete language features 2) a pseudo-philosophical wishy-washy hippie territory where nothing is clear and everything is subjective, where you view all problems as something that is to be modelled with objects, and your own mental model is firmly centred around objects.
Since C lacks concrete OOP features, I will proceed to show why the latter aspect (philosophical OOP) is problematic.
In OOP, objects are entities composed of data and functions that operate on that data. That's what C++ classes usually are: members and methods bound syntactically together. OOP doesn't allow a method to belong to two objects, by definition and by C++ syntax.
Now, let's say you have a C API that offers 2 structs, A and B, and some functions F1A(A, ...), F1B(B, ...), and so on. The people who claim C is OOP language will say "a-ha! A is an object. The data (encapsulated too!) is in type A, and the functions F1A, F2A, ... are the methods. You could write a C++ class 'class A { private: data; public: F1A(...);...};'. Same for B. See? A and B are objects!"
This is the philosophical OOP in action, where "it looks like OOP" becomes "is OOP".
So what happens if the API adds a new function, FAB(A, B, ...), that operates on the internals of A and B? If we switch back to OOP parlance, if you had two classes, A and B, and wanted to add FAB to one of them, which class would FAB belong to?
Well, it's problematic because we're dealing with imaginary objects, so the answer can be whatever you want it to be.
You can arbitrarily say "pick the first parameter type to be the object that owns FAB". Well, what if it's FAB(const char*, A, B)? And even if A is the first one, doesn't FAB changing B break the all-precious object encapsulation? Once you introduce FAB, are these objects at all?
The problem of choosing the "owner" is a common one in C++ OOP because the objects are real (there's syntax to mark something as an object), whereas in C the objects are imaginary and, therefore, a fuzzy ambiguous concept. If you take the set of all functions whose 1st parameter is an int, does that form an object? If everything is an object, is anything an object?
But, like I said, this tends to be a non-problem in C (if you're not a OOP person) because you'll recognise that FAB(A,B) does something useful, and all this conceptual objects design is just noise that does not matter and you're happy to say "my code is not OOP but it does something useful in the best possible way".
Which is why I think that philosophical OOP (object thinking) is dubious, and the only way you can call a language OOP is if it has OOP features. Otherwise, if the only thing you're doing is applying your OOP mindset, I don't think it's fair to say that the language itself is OOP.
I often hear that "FILE is an object, it's exactly as if C++ had class FILE {...}, the class is just syntactic sugar, the result of the compilation is identical".
This is false, as it's not "just syntactic sugar". A C++ class binds a function to a specific struct, and the function can "belong" to no other struct. When doing OOP you have to choose - function F belongs to either object A or to object B. It can't belong to both. Which is why OOP has endlessly silly discussions about whether class Car should Turn() the class Wheel, or should the class Wheel Turn() class Car. Or should we refactor it into class SteeringWheel that will Turn() both Car and Wheel.
This is not a thing in C. Linus Torvalds can add a function F that operates on the internals of both FILE and DIR at the same time, for example, and suddenly it's less clear who "owns" F and which one is the object.
Which is why, personally, I don't think doing dynamic dispatch with void* in C can be qualified as OOP, it's just a pattern that's been there ever since the language was introduced. It's only after you add specific OOP features to the language that it devolves into an OOP language.