45

When a data structure (for example, a queue) is implemented using an OOP language, some members of the data structure need to be private (for example, the number of items in the queue).

A queue can also be implemented in a procedural language using a struct and a set of functions that operate on the struct. However, in a procedural language you can't make the members of a struct private. Were the members of a data structure implemented in a procedural language left public, or was there some trick to make them private?

16
  • 75
    "some members of the data structure needs to be private" There's a big difference between "probably should be" and "needs to be". Give me an OO language and I guarantee I can make a queue that works perfectly fine even with all its members and methods being public, as long as you don't go abusing all that freedom.
    – 8bittree
    Commented Jul 18, 2018 at 20:54
  • 48
    To put a different spin on what @8bittree said, having everything public is just fine if the people using your code are disciplined enough to stick to the interface you've set out. The private member construct came about because of people who couldn't keep their noses out of where they don't belong.
    – Blrfl
    Commented Jul 18, 2018 at 21:42
  • 20
    Did you mean "before encapsulation became popular"? Encapsulation was quite popular before OO languages became popular. Commented Jul 19, 2018 at 0:00
  • 6
    @FrankHileman I think that is actually the core of the question: OP wants to know if encapsulation existed in procedural languages, before Simula/Smalltalk/C++
    – dcorking
    Commented Jul 19, 2018 at 11:00
  • 18
    I am sorry in advance if this comes across as condescending, I don't mean it to be. You need to learn some other languages. Programming languages are not for machines to run, they are for programmers to think in. They necessarily shape the way you think. You simply wouldn't have this question if you'd spent any significant time working with JavaScript/Python/Ocaml/Clojure, even if you did Java all day at your day job. Other than one C++ open source project I work on (that's mostly C anyways) I haven't really used a language with access modifiers since college, and I haven't missed them. Commented Jul 19, 2018 at 17:17

8 Answers 8

140

OOP did not invent encapsulation and is not synonymous with encapsulation. Many OOP languages do not have C++/Java style access modifiers. Many non-OOP languages have various techniques available to offer encapsulation.

One classic approach for encapsulation is closures, as used in functional programming. This is significantly older than OOP but is in a way equivalent. E.g. in JavaScript we might create an object like this:

function Adder(x) {
  this.add = function add(y) {
    return x + y;
  }
}

var plus2 = new Adder(2);
plus2.add(7);  //=> 9

The above plus2 object has no member that would allow direct access to x – it's entirely encapsulated. The add() method is a closure over the x variable.

The C language supports some kinds of encapsulation through its header file mechanism, particularly the opaque pointer technique. In C, it is possible to declare a struct name without defining its members. At that point no variable of the type of that struct can be used, but we can use pointers to that struct freely (because the size of a struct pointer is known at compile time). For example, consider this header file:

#ifndef ADDER_H
#define ADDER_H

typedef struct AdderImpl *Adder;

Adder Adder_new(int x);
void Adder_free(Adder self);
int Adder_add(Adder self, int y);

#endif

We can now write code that uses this Adder interface, without having access to its fields, e.g.:

Adder plus2 = Adder_new(2);
if (!plus2) abort();
printf("%d\n", Adder_add(plus2, 7));  /* => 9 */
Adder_free(plus2);

And here would be the totally encapsulated implementation details:

#include "adder.h"

struct AdderImpl { int x; };

Adder Adder_new(int x) {
  Adder self = malloc(sizeof *self);
  if (!self) return NULL;
  self->x = x;
  return self;
}

void Adder_free(Adder self) {
  free(self);
}

int Adder_add(Adder self, int y) {
  return self->x + y;
}

There is also the class of modular programming languages, which focuses on module-level interfaces. The ML language family incl. OCaml includes an interesting approach to modules called functors. OOP overshadowed and largely subsumed modular programming, yet many purported advantages of OOP are more about modularity than object orientation.

There's also the observation that classes in OOP languages like C++ or Java are often not used for objects (in the sense of entities that resolve operations through late binding/dynamic dispatch) but merely for abstract data types (where we define a public interface that hides internal implementation details). The paper On Understanding Data Abstraction, Revisited (Cook, 2009) discusses this difference in more detail.

But yes, many languages have no encapsulation mechanism whatsoever. In these languages, structure members are left public. At most, there would be a naming convention discouraging use. E.g. I think Pascal had no useful encapsulation mechanism.

10
  • 11
    See the error in Adder self = malloc(sizeof(Adder));? There's a reason typedef-ing pointers and sizeof(TYPE) is generally frowned upon. Commented Jul 18, 2018 at 22:11
  • 10
    You can't just write sizeof(*Adder), because *Adder is not a type, just as *int * is not a type. The expression T t = malloc(sizeof *t) is both idiomatic and correct. See my edit.
    – wchargin
    Commented Jul 19, 2018 at 3:41
  • 4
    Pascal had unit variables that could not be seen from outside that unit. Effectively the unit variables were equivalent to private static variables in Java. Likewise to C you could use opaque pointers to pass data in Pascal without declaring what it was. Classic MacOS used a lot of opaque pointers since public and private parts of a record (data structure) might be passed together. I remember Window Manager doing a lot of this since parts of the Window Record were public but some internal information was included as well. Commented Jul 19, 2018 at 14:21
  • 6
    Perhaps a better example than Pascal is Python, which supports object-orientation but no encapsulation, resorting to naming conventions such as _private_member and output_property_, or more advanced techniques for making imutable objects.
    – Mephy
    Commented Jul 19, 2018 at 14:56
  • 11
    There's an annoying tendency in OOD literature to present every design principle as an OO design principle. The (non-academic) OOD literature tends to paint a picture of a "dark ages" where everyone was doing everything wrong, and then the OOP practitioners bring the light. As far as I can tell, this mostly derives from ignorance. For example, as far as I can tell, Bob Martin gave functional programming a serious look only a few years ago. Commented Jul 20, 2018 at 17:08
31

First, being procedural versus object oriented has nothing to do with public vs private. Plenty of object oriented languages have no notion of access control.

Secondly, in "C" - which most people would call procedural, and not object oriented, there are lots of tricks you can use to effectively make things private. A very common one is to use opaque (e.g. void*) pointers. Or - you can forward declare an object, and just not define it in a header file.

foo.h:

struct queue;
struct queue* makeQueue();
void add2Queue(struct queue* q, int value);
...

foo.c:

struct queue {
    int* head;
    int* head;
};
struct queue* makeQueue() { .... }
void add2Queue(struct queue* q, int value) { ... }

Look at the windows SDK! It uses HANDLE and UINT_PTR, and things like that to be generic handles to memory used in APIs - effectively making the implementations private.

9
  • 1
    My sample demonstrated a better (C) approach - using forward declared structs. to use the void* approach I would use typedefs: In the .h file say typedef void* queue, and then everywhere we had struct queue just say queue; Then in the .c file, rename struct queue to struct queueImpl, and any arguments become queue (isntead of struct queue*) and the first line of code for each such function becomes struct queueImpl* qi = (struct queueImpl*)q Commented Jul 18, 2018 at 21:52
  • 7
    Hmm. It makes it private because you cannot access (read or write) any fields of the 'queue' from anyplace besides its implementation (foo.c file). What else did you mean by private? BTW - thats true for BOTH the typedef void* apporach and the (better) forward declare struct approach Commented Jul 18, 2018 at 22:00
  • 5
    I must confess its been almost 40 years since I read the book on smalltalk-80, but I don't recall any notion of public or private data members. I think CLOS also had no such notion. Object Pascal had no such notion. I recall Simula did (probably where Stroustrup got the idea), and most OO languages since C++ have it. Anyhow - we agree encapsulation and private data are good ideas. Even the original questioner was clear on that point. He was just asking - how did oldies do encapsulation in pre-C++ languages. Commented Jul 19, 2018 at 1:15
  • 5
    @LewisPringle there is no mention of public data members in Smalltalk-80 because all "instance variables" (data members) are private, unless you use reflection. AFAIU Smalltalkers write an accessor for every variable they want to make public.
    – dcorking
    Commented Jul 19, 2018 at 11:05
  • 4
    @LewisPringle by contrast, all Smalltalk "methods" (function members) are public (there are clumsy conventions for marking them private)
    – dcorking
    Commented Jul 19, 2018 at 11:07
13

"Opaque data types" was a well-known concept when I did my computer science degree 30 years ago. We did not cover OOP as it was not in common usage at the time and "functional programming" was considered to be more correct.

Modula-2 had direct support for them, see https://www.modula2.org/reference/modules.php.

It has already been explained by Lewis Pringle how forward declaring a struct can be used in C. Unlike Module-2, a factory function had to be provided to create the object. (Virtual methods were also easy to implement in C by having the first member of a struct being a pointer to another struct that contained function pointers to the methods.)

Often convention was also used. For example, any field starting with “_” should not be accessed outside of the file that owned the data. This was easily enforced by the creation of custom checking tools.

Every large scale project I have worked on, (before I moved onto C++ then C#) had a system in place to prevent "private" data being accessed by the wrong code. It was just a little less standardized than it is now.

8

Note there are many OO languages without a built-in ability to mark members private. This can be done by convention, without a need for the compiler to enforce privacy. For example, people will often prefix private variables with an underscore.

There are techniques to make it harder to access "private" variables, the most common being the PIMPL idiom. This puts your private variables in a separate struct, with just a pointer allocated in your public header files. This means an extra dereference and a cast to get any private variables, something like ((private_impl)(obj->private))->actual_value, which gets annoying, so in practice is rarely used.

5

Data structures didn't have "members", only data fields (assuming it was a record type). Visibility was typically set for the entire type. However, that may not be as limiting as you think, because the functions weren't part of the record.

Let's drop back and get a little bit of history here...

The dominant programming paradigm before OOP was called structured programming. The initial main goal of this was to avoid use of unstructured jump statements ("goto"s). This is a control flow oriented paradigm (while OOP is more data-oriented), but it was still a natural extension of it to attempt to keep data logically structured just like the code.

Another outshoot of structured programming was information hiding, the idea that the implementations of the code's structure (which is likely to change fairly often) should be kept separate from the interface (which ideally won't change nearly as much). It's dogma now, but in the olden days, many people actually considered it better for every developer to get to know the details of the entire system, so this was at one time actually a controversial idea. The original edition of Brook's The Mythical Man Month actually argued against information hiding.

Later programming languages designed explicitly to be good Structured Programming languages (for example, Modula-2 and Ada) generally included information hiding as a fundamental concept, built around some kind of concept of a cohesive facility of functions (and any types, constants, and objects they might required). In Modula-2 these were called "Modules", in Ada "Packages". A lot of modern OOP languages call the same concept "namespaces". These namespaces were the organizational foundation of development in these languages, and for most purposes could be used similarly to OOP classes (with no real support for inheritance, of course).

So in Modula-2 and Ada (83) you could declare any routine, type, constant, or object in a namespace private or public, but if you had a record type, there was no (easy) way to declare some record fields public and others private. Either your whole record is public, or it's not.

3
  • I spent quite a bit of time working in Ada. Selective hiding (of part of a data type) was something we did all the time; in the containing package, you'd define the type itself as private or limited private; the package interface would expose public functions/procedures to get and/or set internal fields. Those routines would of course need to take a parameter of the private type. I did not then and do not now consider this difficult.
    – David
    Commented Jul 20, 2018 at 15:04
  • Also, AFAIK most OO languages work the same way under the hood, i.e. myWidget.getFoo() is really implemented as getFoo(myWidget). The object.method() invocation is just syntactic sugar. Important IMHO -- see Meyer's Principle of Uniform Access / Reference -- but still just syntactic sugar.
    – David
    Commented Jul 20, 2018 at 15:07
  • @David - That was the Ada community's argument for years during the Ada 95 era. I believe they finally gave in and proved their own argument by allowing object.method() as an alternative form for method(object, ...) for people who just couldn't make the conceptual leap.
    – T.E.D.
    Commented Jul 20, 2018 at 18:19
0

In C you could already pass around pointers to declared but undefined types as others have said, in effect restricting access to all fields.

You can also have private and public functions on a module-to-module basis. Functions declared static in the source file are not visible to the exterior, even if you attempt to guess their name. Similarly, you can have static file-level global variables, which is generally bad practice but allows isolation on a module basis.

It's probably important to stress that access restriction as a well standardized convention rather than a language-enforced construct works just fine (see Python). On top of that, restricting access to object fields is only ever going to protect the programmer when there is a need to change the value of the data inside an object after creation. Which is already a code smell. Arguably, C's and in particular C++'s const keyword for methods and function arguments is a far greater help to the programmer than Java's rather poor final.

1
  • The only feature C had that was specifically for information hiding was static global data and operations (which meant they weren't presented to the linker for use from other compilations). You can plausibly argue any support C had for good software design practices aside from that was pretty much a hack, and not part of the original design of the language back in 1972.
    – T.E.D.
    Commented Jul 20, 2018 at 19:24
0

If your definition of Public is the ability to access implementation and data/properties via your own code at any point, the answer is simply: Yes. However, it was abstracted by diverse means - depending on the language.

I hope this succinctly answered your question.

-1

Here is a very simple counter-example: in Java, interfaces define objects, but classes do not. A class defines an Abstract Data Type, not an object.

Ergo, any time you use private in a class in Java, you have an example of a data structure with private members that is not object-oriented.

3
  • 7
    This answer is of course technically correct, but it is totally incomprehensible to anyone who doesn't already know how what ADTs are and how they are different from objects.
    – amon
    Commented Jul 19, 2018 at 9:40
  • 1
    I learned something from this answer.
    – littleO
    Commented Jul 19, 2018 at 15:06
  • 3
    Interfaces do not "define" objects; they specify contracts for operations/behaviors that objects can do or perform. Just like inheritance is generally described by an is a relationship and composition by a has a relationship, interfaces are generally described by can do relationships.
    – code_dredd
    Commented Jul 19, 2018 at 23:32

Not the answer you're looking for? Browse other questions tagged or ask your own question.