6

I am not sure that what I am trying to do is called encapsulation, but it's an OOP concept. I am implementing a binary tree and in particular the insert function:

typedef struct __node* tree;
typedef struct __node { void* data; tree l,r; } node;

typedef struct {int (*cmp)(void* a,void* b); tree root;} avl_tree;

....

void tree_insert(tree node, tree* root, int (*cmp)(void* a,void* b))
{
  if (*root==NULL) { *root=node; return; }
  int c1 = cmp(node->data, (*root)->data);

  if (c1==-1) tree_insert(node, &((*root)->l), cmp);
}

tree tree_new_node(void*data){ tree a = malloc(...); ... return a; }

void avl_insert(void* data, avl_tree* a)
{
  tree_insert(tree_new_node(data), &(a->root), a->cmp);
  ....
}

The module is to be used through the avl_insert function which is given a pointer to the relevant balanced tree avl_tree which contains the pointer to the raw tree as well as a pointer to comparator. Now, it should obviously call tree insert and tree_insert should have access to the comparator as well as to the node I am currently inserting. The function walks on a binary tree so it's naturally recursive. However, if I give it the comparator and the current node as parameters they will be passed with each recursive invocation which is not necessary since they will always be the same.

I would like to avoid having to do so. I have not been able to come up with a clean and nice solution. These are the options that I could think of:

  1. Use a C++ class and have the tree_insert function be a method of avl_tree class. Then it would have access to the comparator through the this pointer. The problem with this solution is that I want to use C not C++. Besides, it won't eliminate the passing of the current node parameter.

  2. Use static members inside the function (or global data). I am not sure I can cleanly initialize them at each avl_insert call. Besides, this solution is not thread safe.

Now that I think about it this seems very easy to implement in a functional programming language. I wonder, is this a fundamental problem with C or is it just me not knowing how to do it. What would be the cleanest way to achieve this?

Thank you!


After I thought about Victor Sorokin's answer I read about the this pointer and it turns out it is an implicit parameter in every member function call. Now that I think about it it seems the only logical solution. Each invocation of the tree_insert function needs to know the address of the structure it's operating on. Not even in a functional language could you avoid that extra pointer...

A possible solution would be to keep a pointer to the main tree structure in each node..

So it's a fundamental "problem".

Deqing
  • 14,098
  • 15
  • 84
  • 131
  • if you want OOP, use C++ instead of C – Nate Koppenhaver May 13 '11 at 20:50
  • 1
    C doesn't have true encapsulation. Like Python, any privacy relies on programmer maturity. – Rafe Kettler May 13 '11 at 20:52
  • thinking about Reflection and similar features there is often no such thing as "true encapsulation"... – Gregor May 13 '11 at 20:55
  • I do not want OOP. I simply wanted to avoid having to pass seemingly unnecessary parameters around in my recursive function. Also I did not care about any hiding. My question is more low level. Thank you. – Svetoslav Kolev May 13 '11 at 21:04
  • @RafeKettler: Encapsulation is not the same as data protection, so your statement about "true" encapsulation not being available in Python is actually not true, or at least not less so than in Java or C++, for the reason Gregor pointed out. Encapsulation is a means to help manage boundaries of responsibility, not to keep the programmer from intentionally shooting himself in the foot. – Erik Kaplun Feb 18 '13 at 18:34
  • Note that the double-underscore prefix is reserved for use by 'the implementation'. Do not create such names in your own code. Only use names provided by the implementation — and only if their use is documented. See [§7.1.3 Reserved Names](http://port70.net/~nsz/c/c11/n1570.html#7.1.3) in the standard for the full details (beware leading underscores in general). – Jonathan Leffler Dec 18 '17 at 00:08

3 Answers3

1

One fun approach that could be used to achieve encapsulation is looking into assembly code emitted by C++ compiler and then translating it into appropriate C code.

Another, more conventional, approach would be to use some C object library, like GLib.

I think, though, these two methods will give similar results :)

By the way, first option you mentioned is just as vulnerable to threading issues as second. There's no implicit thread-safety in C++.

"OOP" C code I have seen in Linux kernel (file-system layer) is mostly concerned with polymorphism, not with encapsulation. Polymorphism is achieved by introducing structure enumerating possible operations (as pointers to functions). Various "subclasses" then created, each initializing this structure with it's own set of implementation methods.

Victor Sorokin
  • 11,878
  • 2
  • 35
  • 51
  • For the first option, maybe it may be more viable to use a compiler that compiles C++ code in C, like Comeau. – Matteo Italia May 13 '11 at 20:54
  • @Matteo Didn't know about such possibility. Unfortunately, I haven't been programming in C for years... – Victor Sorokin May 13 '11 at 20:56
  • Oh yes, actually, I've just remembered that first C++ compiler written by Stroustrup was actually translator from C++ to C :) – Victor Sorokin May 13 '11 at 20:59
  • Actually, after I thought about your comment I read about `this` pointer and it turns out it is an implicit parameter in every member function call. Now that I think about it it seems the only logical solution. Each invocation of the `tree_insert` function needs to know the address of the structure it's working on. Not even in a functional language could you avoid that extra pointer... – Svetoslav Kolev May 13 '11 at 21:07
  • A possible solution would be to keep a pointer to the main tree structure in each node... – Svetoslav Kolev May 13 '11 at 21:08
0

There is already a question covering my answer—What does “static” mean in a C program?


You can roughly take a C source file as a class. The keyword static makes the variable or function have only internal linkage, which is similar to private in classical OOP.

foo.h

#ifndef FOO_H
#define FOO_H

double publicStuff;

double getter (void);
void setter (double);
int publicFunction (void);

#endif

foo.c

#include "foo.h"

static double privateStuff;

static int privateFunction (void)
{
    return privateStuff;
}

int publicFunction (void)
{
    return privateFunction();
}

double getter (void)
{
    return privateStuff;
}

void setter (double foo)
{
    privateStuff = foo;
}

main.c

#include "foo.h"
#include <stdio.h>

static double privateStuff = 42;

static int privateFunction (void)
{
    return privateStuff;
}

int main (void)
{
    publicStuff = 3.14;

    setter(publicStuff);

    printf("%g %d %d\n", getter(), publicFunction(), privateFunction());

    return 0;
}
jdh8
  • 3,030
  • 1
  • 21
  • 18
0

You should be able to convert that tail recursion to iteration, and avoid the function calls altogether. Something like

void tree_insert(tree node,tree*root,int (*cmp)(void*a,void*b))
{
   tree* current = root;
   while (*current != NULL)
   {
     int c1=cmp(node->data,(*current)->data);
     if(c1==-1)current = &((*current)->l);
     else current = &((*current)->r);
   }
   *current=node;
 }  
AShelly
  • 34,686
  • 15
  • 91
  • 152
  • Unfortunately my intent could not be achieved with tail-recursion. The code snippet I showed was an incomplete function. I need to do more work on the current node after I insert stuff in its children (balance the tree). – Svetoslav Kolev May 13 '11 at 22:26
  • However, that gives me an idea: Explicit stack management (no recursive function calls) would be best performance-wise. However, in such case you would need a stack structure which complicates the problem. Thank you! – Svetoslav Kolev May 13 '11 at 22:28