Monday, November 1, 2010

Pimples are annoying, C++ Clearasil wanted

Hating C++ seems to be the cool thing to do these days and though I can understand why the sentiment exists, I don't share it. One area I often think is lacking though is the ability to really hide private data without reverting to writing C. Not that I think there's anything wrong with writing C but in a codebase that's largely C++ it can look somewhat out of place.

Idiomatic C and C++ differ quite significantly in the way interfaces are exposed to end users. In many cases with C people use declared structures as opaque handles, preventing end users from mucking around with internal structures. External header files for libraries end up looking something like this:

struct foo;
struct foo *foo_create(int a);
void foo_destroy(struct foo *foo_instance);
int foo_method(struct foo *foo_instance, const char *string, int count);

As a user of this code I have no idea what state foo encapsulates because the library author has made it clear that it isn't something that I should worry about.

C++, though, encourages exposing data by bundling all types and methods of a class into the public declaration.

class foo {
const char *m_string;
int m_count;
bar &m_barInstance;

public:
foo(int a);
~foo();
int method(const char *string, int count);
};

The world now knows what foo contains, whether they need to or not. The idiomatic method of hiding internal details in C++ is to use pointers-to-implementation (pimpl) patterns. These do clean up the class declaration a bit:

struct foo_impl;
class foo {
foo_impl *m_implementation;

public:
foo(int a);
~foo();
int method(const char *string, int count);
};

And behind the scenes, in foo.cpp:

struct foo_impl {
const char *m_string;
int m_count;
bar &m_barInstance;
};

foo::foo(int a)
: m_implementation(new foo_impl)
{
m_implementation->m_count = a;
};


The two big downsides with pimpls are that now every allocation of foo comes with a second allocation of foo_impl, and also that our code is littered with m_implementation.

What I wish C++ had was a way to export the public interface of a class without any consideration to storage or to its own private functions. Something like this:

static_interface foo {
int method(const char *string, int count);
};

I really want to avoid having the extra indirection of a pimpl pointer or indirect jump if it were to just be an interface but while still having the convenience of being able to write foo_ptr->method("hello", 5). This would mean that it could only be used as a pointer/reference but that's no different than with the C example above.

2 comments:

  1. Agreed, this is a serious weakness in C++ and something that makes C-style interface in some cases so much faster, cleaner & more secure.

    Although having the C++ access syntax would definitely be preferable, just don't want everyone to build and see my dirty internal details. Nor have to rebuild all code using my class when I change internals.

    Not a fan of all the copy'n'paste pImpl requires either

    ReplyDelete
  2. If I recall modula-3 could do this, you would implement a module and then expose it through several different interfaces. The details are hazy though (17 years ago), and at the time I did not wonder what the internal implementation was like, whether this mechanism was able to statically bind everything or if it had to resort to late binding.

    Best way is not to have too many internal details so that exposing them isn't a big deal! I know, easy to say, most code bases end up with lots of state in the classes.

    I think that if we mostly concern ourselves with data and operations, the operation can be a minimalistic interface and the data not known outside of the operation(s) (but intimately known within it). Not all code is easy to express that way...

    ReplyDelete