Friday, June 22, 2012

Plain C with RAII idiom


One day I was pushed into situation that I must write in pure old-scool plain C. Nova days, modern C++ is sky-rocketing with Standards Comittee initiatives but cross-platform development is another world. So you have to forget templates, Boost and even STL. But most painful for me as "clean code" adept was RAII absence: no wrapper classes, no auto-generated cleanup in scope epilog etc... But with some trade-off in mind, you can get RAII then!

Ask Google!

Indeed, this is ultimate answer. Google could find anything and I hope it could include this article into "plain c raii" search results soon :)
Meanwhile, there are other options found:
  • use C++ and transform it into plain C then (kind of CFront compiler)
  • __attribute__((cleanup (scope_leaving))), supported by GCC only
Both of them are innatural, at least from my point. And both do some severe limitations which are not for particular project. We should go deeper...

Do It Yourself

Let investigate, why we need RAII. What are reasons? What are benefits? Let me start from last question first. RAII gives us shorter code and guaranteed destruction of allocated resource. So far so good. What about reasons? OK, our world is not 100% perfect so you have to exit abnormally. Or exit prematurely. Or forced to exit by exception. So, there are many bad things that can occur and we should have a way to manage them:
  char *p = create_string();
  if (!validate_string(p)) {
      delete_string(p);
      return;
  }
  if (!process_string(p) {
      delete_string(p);
      return;
  }
  print_string(p);
  delete_string(p);

Ugly code, right? In real life, this stinky chunk would progress fast into something non-maintenable, leaky and messy. But what if we could wrap this into fictional plain-C RAII? The code should become simpler:
  if (!validate_string(p)) {
      return;
  }
  if (!process_string(p) {
      return;
  }
  print_string(p);

This time, it almost OK, right? The only thing out of this listing is how to create and destroy our resource.

X-RAII: the scope is out there

Plain C gives us one big advantage: code flow is really stright, with no exceptions raising nor hidden code inserted. So, let imagine this:
  char *p = create_string();
  foo(p); // ...some magic goes here...
  delete_string(p);

Yes, this is the trick: do resource management manually by yourself but keep it out of actual code. In ideal case, you should have 3 lines written: initialization, usage, cleanup. So, our exercise finally would look as following:
void foo(char *p) {
  if (!validate_string(p)) {
      return;
  }
  if (!process_string(p) {
      return;
  }
  print_string(p);
}

int main() {
  // do RAII magic here:
  char *p = create_string(); // initialization
  foo(p); // usage
  delete_string(p); // cleanup

  return 0;
}

Afterburner

We did hit the target, what about cost? Don't be enchanted - there is no silver bullet again. For example, you can't arrange kind of smart-pointers this way (if "yes" - could you leave me a note?). Apparently, simultaneous management of two (or more) resources is a headache. From other hand, if you're not mighty jedi, it is good reason to think-first how to split your resources as separately-managed. One more good thing you can do factory methods, just put foo() as a parameter of your RAII magic function (piece of my home-made code below):

void socket_envelope(struct sock_calls_stub *sock_calls,
    void (*workitem)(struct sock_calls_stub *sock_calls, SOCKET sock),
    int sock_type, int sock_protocol) {
  int set_option_on = 1;
  int sock = socket(AF_INET, sock_type, sock_protocol);
  assert_true(INVALID_SOCKET != sock);
  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&set_option_on,
    sizeof(set_option_on));

  workitem(sock_calls, sock);
  closesocket(sock);
}

P.S. You can find this approach as too spartan. THIS IS SPARTAN RAII!!!

No comments:

Post a Comment