Forward Declarations

E

ena8t8si

Keith said:
In that case, it would probably make sense to hide the *alloc() and
free() calls behind some type-specific functions; the *alloc()
wrappers could also do any necessary initialization of the struct
members. This is basically what fopen() and fclose() do, except that
the pointer isn't hidden behind a typedef; the members of the
structure are effectively hidden not by the fact that there's no name
for the structure type (there is, FILE), but by the fact that the
details are undocumented, and code that depends on them will break
when ported.

It depends what you want to achieve.

If you want the structure members accessed only
within the implementing module, then something
like

typedef struct __FILE FILE;

in a header, and a subsequent

struct __FILE { ... };

in the implementing module is usually a good way
to do that. (User code shouldn't use the __'s,
other than that it's the same.)

If what you want is to guarantee that struct
instances are made only through *alloc(), then
the idiom

typedef struct { ... } *Foo;

is a way to do that. C gives you a choice,
but it has to be one or the other, it can't
be both.
 
M

Michael Wojcik

Usually the best argument in favor of using a typedef
is that the name defined reflects how you want client
code to view the type.

If client code should view the type as opaque, then
using typedef for a struct type makes sense.

Perhaps, but since C already contains an idiom for opaque types which
does not require a typedef, the typedef is still superfluous.

When I have a header file that contains:

struct foo;
struct foo *MakeFoo(void);
void UseFoo(struct foo *);

I know I am dealing with an opaque type. No typedef need apply.

What's more, I know that I am dealing with a pointer-to-incomplete-
structure, which is useful if, for example, I want to insert some
temporary instrumentation that tracks the struct-foo objects I have
created; I know I can print some representation of a struct foo *
with the %p format specifier (after casting it to void *), for
example. That's much better than a typedef that hides the fact that
I am dealing with a pointer, which I realize is not what you
suggested, but is all too common among the fans of typedef.
 
E

ena8t8si

Michael said:
Perhaps, but since C already contains an idiom for opaque types which
does not require a typedef, the typedef is still superfluous.

When I have a header file that contains:

struct foo;
struct foo *MakeFoo(void);
void UseFoo(struct foo *);

I know I am dealing with an opaque type. No typedef need apply.

You're using the term opaque in different
sense than I was. As I was using the term,
opaque means a type that client code should
know nothing about. The "opaque type" that
you suggest requires client code to know
(a) that it's a pointer and (b) that the
pointer points to a struct. That's more
translucent than opaque.

For translucent types, struct foo * is ok.
A type intended to be really opaque should
use typedef.
 
M

Michael Wojcik

You're using the term opaque in different
sense than I was.

Perhaps I am, but I find the distinction you draw trivial and
superfluous.
As I was using the term,
opaque means a type that client code should
know nothing about.

This cannot be achieved in C. The "client code" can still apply the
sizeof operator to a typedef, for example. Assuming an implementation
with decent QoI, trial code can be written to determine what types the
typedef is compatible with, by observing diagnostics.
The "opaque type" that
you suggest requires client code to know
(a) that it's a pointer and (b) that the
pointer points to a struct.

So what? In some languages (eg OCaml), essentially all user-defined
types are references to structured types. That doesn't seem to be a
problem for them. I'll be damned if I can think why this would be a
problem in C. What precious information is leaking out of the abstract
interface?
That's more translucent than opaque.

The size and nature of the contents are invisible outside the
interface. That's as good a definition of "opaque type" as any other
I've seen.
A type intended to be really opaque should
use typedef.

You're welcome to that opinion. I don't find it convincing.

I'll also note in passing that using typedefs for "opaque" (abstract)
types encourages not wrapping elementary types in structs. And that,
in turn, may lead to maintenance problems, if the type needs to be
extended later, but existing callers have divined its non-struct
nature (eg by reading headers) and written code that relies on that
fact.

If struct is your only type-abstraction mechanism, this is never an
issue.

--
Michael Wojcik (e-mail address removed)

He smiled and let his gaze fall to hers, so that her cheek began to
glow. Ecstatically she waited until his mouth slowly neared her own.
She knew only one thing: rdoeniadtrgove niardgoverdgovnrdgog.
 
E

ena8t8si

Michael said:
Perhaps I am, but I find the distinction you draw trivial and
superfluous.

As long as you understand the distinction, you can respond
to what I'm actually saying and not something else.
This cannot be achieved in C. The "client code" can still apply the
sizeof operator to a typedef, for example. Assuming an implementation
with decent QoI, trial code can be written to determine what types the
typedef is compatible with, by observing diagnostics.

Would you prefer "a type that client code should know as
little about as possible"?

In any case that's not really the issue. The question is
not whether client code _can_ know something, but whether it
_must_ know something. An opaque type is one where
knowledge of the type's representation should be confined to
the module that supplies/implements it.
So what? In some languages (eg OCaml), essentially all user-defined
types are references to structured types. That doesn't seem to be a
problem for them. I'll be damned if I can think why this would be a
problem in C. What precious information is leaking out of the abstract
interface?

If the type is defined with a typedef, essentially no
representation information has to leak out.

If there isn't a typedef, then representation information is
forced to leak out, as explained above.
The size and nature of the contents are invisible outside the
interface. That's as good a definition of "opaque type" as any other
I've seen.

You seem to want to argue about what makes a good definition
for "opaque type". Either you're missing the point of what
I'm saying, or you're just being deliberately difficult.
You're welcome to that opinion. I don't find it convincing.

I expect that's because you insist on reading "opaque" in
the sense you want to use it rather than as I have been
using it.
I'll also note in passing that using typedefs for "opaque" (abstract)
types encourages not wrapping elementary types in structs. And that,
in turn, may lead to maintenance problems, if the type needs to be
extended later, but existing callers have divined its non-struct
nature (eg by reading headers) and written code that relies on that
fact.

If struct is your only type-abstraction mechanism, this is never an
issue.

Your reasoning here starts with a false premise.
 
C

Chris Torek

[on opaque types, and "typedef" vs "struct"]

In any case that's not really the issue. The question is
not whether client code _can_ know something, but whether it
_must_ know something. An opaque type is one where
knowledge of the type's representation should be confined to
the module that supplies/implements it.

Indeed -- but the problem with "typedef" is that, in C, you must
know whether the typedef-name is a synonym for an array type,
because array types do not behave the same as any other type.

Hence, typedef is not at all useful for opaque types in C, unless
you also add a (programmer-imposed) constraint that no typedef ever
names an array type.

If you simply make every opaque type a structure -- no pointer is
required -- you get the desired effect:

struct user_type_0;
struct user_type_1;
struct user_type_2;

All three types are unique and well-behaved, and the client code
need not know anything at all. If we "peek behind the curtain",
we might find, e.g.:

struct user_type_0 { double val; };
struct user_type_1 { double val; };
struct user_type_2 { struct u2_implementation *ptr; };

but all the user knows about them is that each one behaves like
other primitive types in C (of course, one can never apply arithmetic
operators directly, for instance).
 
E

ena8t8si

Chris said:
[on opaque types, and "typedef" vs "struct"]

In any case that's not really the issue. The question is
not whether client code _can_ know something, but whether it
_must_ know something. An opaque type is one where
knowledge of the type's representation should be confined to
the module that supplies/implements it.

Indeed -- but the problem with "typedef" is that, in C, you must
know whether the typedef-name is a synonym for an array type,
because array types do not behave the same as any other type.

Hence, typedef is not at all useful for opaque types in C, unless
you also add a (programmer-imposed) constraint that no typedef ever
names an array type.

If you simply make every opaque type a structure -- no pointer is
required -- you get the desired effect:

struct user_type_0;
struct user_type_1;
struct user_type_2;

All three types are unique and well-behaved, and the client code
need not know anything at all. If we "peek behind the curtain",
we might find, e.g.:

struct user_type_0 { double val; };
struct user_type_1 { double val; };
struct user_type_2 { struct u2_implementation *ptr; };

but all the user knows about them is that each one behaves like
other primitive types in C (of course, one can never apply arithmetic
operators directly, for instance).

Your comments don't make sense to me. If I'm defining
an opaque type that needs an array representation, the
type can be defined as an array wrapped in a struct:

typedef struct { whatever_t v[SOME_N]; } opaque;

If I'm defining an opaque type that shouldn't be assigned
or have its value passed directly, but always by address,
the type can be defined wrapping the representation type
in an array:

typedef whatever_t opaque[1];
void assign_opaque( opaque *, opaque * );

...

opaque o1, o2;
assign_opaque( &o1, &o2 ); /* can't do o1 = o2; */

If I'm defining an opaque type that needs to have
some scalar operations exposed (eg, == 0), the type
can be defined in a usual way:

typedef struct some_struct_t *opaque;

...

opaque o1;
if(!o1) ...

If I'm defining an opaque type that's a scalar type
but should _not_ have scalar operations exposed, the
type can be wrapped in a struct:

typedef struct { scalar_t v; } opaque;

And finally, if I'm defining an opaque type that's
a scalar type that shouldn't be assigned and also
shouldn't be accidentally turned into a pointer to
its scalar representation, the type can be wrapped
in struct wrapped in an array:

typedef struct { scalar_t v; } opaque[1];

In all of these cases the client code uses just the
name that was typedef'ed. Using typedef is more
flexible than always using struct, not less flexible.
 
C

Chris Torek

f I'm defining an opaque type that shouldn't be assigned
or have its value passed directly, but always by address,
the type can be defined wrapping the representation type
in an array:

typedef whatever_t opaque[1];
void assign_opaque( opaque *, opaque * );

This argument did occur to me later today (while at the gym),
and is one in favor of typedef with nothing *but* arrays, since
using any other type allows ordinary assignment:

T a, b;
...
b = a; /* error if T is an alias for an array type */

which is in fact letting information (namely, "simple assigmnent
is OK") leak out of the abstraction. But C confounds this, at
least to some extent:

void f(T a, T b) {
...
b = a; /* oops, succeeds after all */
...
}

through the "array rewritten as pointer" rule for parameters.

(This is the sort of problem I mean to indict, regarding arrays:
their "array-ness" leaks out in ways one does not always intend.
Again, this particular problem can be avoided through programmer
discipline; but if you are going to call upon discipline, you can
simply dispense with opaque types in the first place. :) )

(As a side comment, a lot of my instincts here were set back in
the Bad Old Days when "&array" was diagnosed, instead of producing
a pointer to the entire array.)
 
E

ena8t8si

Chris said:
f I'm defining an opaque type that shouldn't be assigned
or have its value passed directly, but always by address,
the type can be defined wrapping the representation type
in an array:

typedef whatever_t opaque[1];
void assign_opaque( opaque *, opaque * );

This argument did occur to me later today (while at the gym),
and is one in favor of typedef with nothing *but* arrays, since
using any other type allows ordinary assignment:

T a, b;
...
b = a; /* error if T is an alias for an array type */

which is in fact letting information (namely, "simple assigmnent
is OK") leak out of the abstraction. But C confounds this, at
least to some extent:

void f(T a, T b) {
...
b = a; /* oops, succeeds after all */
...
}

through the "array rewritten as pointer" rule for parameters.

Excellent point. No good way around this problem that I can
see.
(This is the sort of problem I mean to indict, regarding arrays:
their "array-ness" leaks out in ways one does not always intend.
Again, this particular problem can be avoided through programmer
discipline; but if you are going to call upon discipline, you can
simply dispense with opaque types in the first place. :) )

I don't think of it as an either/or situation. Many (most?)
programming techniques benefit from both discipline and some
level of automated support. A combination of the two is
usually better than either individually.

Furthermore, much of the motivation (as I see it) for using
opaque types is to protect against a change in representation.
If a typedef is used in such cases, then it's easier to switch
between the different scenarios given in my previous message.
Using some struct type as the declaring type limits some options.
Of course, sometimes limiting those options is just what you
want, and in cases like that using struct is a better expression
of the intent. In most cases though I usually want to preserve
as much representation flexibility as I can.
(As a side comment, a lot of my instincts here were set back in
the Bad Old Days when "&array" was diagnosed, instead of producing
a pointer to the entire array.)

Of course I'm guessing, but I conjecture that a difference
in our respective backgrounds could account for a lot of
the difference in our viewpoints. I was already very well
grounded in abstract data types and information hiding by
the time I started programming in C; using typedef to define
abstract/opaque types is something I did probably literally
on day one of my C programming experience.

None of which is meant to say anything negative about anyone with
a different background. I enjoy and benefit from the comments
that come out of your experience and background.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top