10

Background/Scenario

I started writing a CLI application purely in C (my first proper C or C++ program that wasn't "Hello World" or a variation thereof). Around midway through I was working with "strings" of user input (char arrays) and I discovered the C++ string streamer object. I saw that I could save code using these, so I used them through the application. This means that I have changed the file extension to .cpp and now compile the app with g++ instead of gcc. So based on this, I would say the application is now technically a C++ application (although 90%+ of the code is written in what I would call C, as there is lots of cross-over between the two language given my limited experience of the two). It is a single .cpp file around 900 lines long.

Important Factors

I want the program to be free (as in money) and freely distributable and usable for all to have. My concern is that someone will look at the code and think something to the effect of:

Oh look at the coding, it's awful, this program can't help me

When potentially it could! Another matter is the code being efficient (it is a program for testing Ethernet connectivity). There should be no parts of the code that are so inefficient that they can severely hinder the performance of the application or its output. However, I think that is a question for Stack Overflow when asking for help with specific functions, methods, object calls, etc.

My question

Having (in my opinion) mixed C and C++ where perhaps I shouldn't. Should I look to rewrite it all in C++ (by this, I mean implement more C++ objects and methods where perhaps I have coded something in a C style that can be condensed using newer C++ techniques), or remove the use of string streamer objects and bring it all "back" to C code? Is there a correct approach here? I am lost and need some guidance on how to keep this application "Good" in the eyes of the masses, so they will use it and benefit from it.

The Code - Update

Here is a link to the code. It is circa 40% comments, I comment nearly every line until I feel more fluent. In the copy I have linked to though, I have removed pretty much all the comments. I hope this doesn't make it too hard to read. I am hoping though that no one should need to fully understand it. If I have made fatal design flaws though, I am hoping they should be identifiable easily. I should also mention, I am writing a couple of Ubuntu desktops and laptops. I'm not intending to port the code to other operating systems.

4
  • 3
    I disambiguated CLI for you. CLI can also refer to Common Language Infrastructure, which doesn't make much sense from a C perspective. Jun 10, 2013 at 19:38
  • You could rewrite it in FORTRAN.I never heard of OOF.
    – ott--
    Jun 10, 2013 at 20:18
  • 2
    I wouldn't worry too much about people not using your software if it isn't "pretty". 99% of your users won't even look at the code or care about how it's written so long as it accomplishes what they need it to do. It is, however, important that the code be consistent, etc. to help you in maintaining it long term.
    – Evicatos
    Jun 10, 2013 at 22:24
  • 1
    Why don't you make your thing free software (e.g. under GPLv3 license), with the code on e.g. github. Your code is lacking a LICENSE file. You might get interesting feedback. Jun 28, 2015 at 16:26

7 Answers 7

13

Let's start at the beginning: mixed C and C++ code is fairly common. So you're in a big club to start with. We have huge C codebases in the wild. But for obvious reasons many programmers refuse to write at least new stuff in C, having access to C++ in the same compiler, new modules start to be written that way -- at first just leaving the existing parts alone.

Then eventually some existing files get recompiled as C++, and some bridges can be deleted... But it may take really long time.

You are ahead somewhat, your full system is now C++, just most of it is written "C-style". And you see mix of styles a problem, what you should not: C++ is a multi-paradigm language supporting many styles, and allow them to co-exist for good. Actually that is the main strength, that you are not forced to a single style. One that would be suboptimal here and there, with some luck not everywhere.

Re-working the codebase is a good idea, IF it is broken. Or if it is in the way of development. But if it works (in the original sense of word), please follow the most basic engineering principle: if it ain't broke, don't fix it. Leave the cold parts alone, put your effort where it counts. On the parts that are bad, dangerous -- or in new features, and just refactor parts to make them a bed.

If you seek general things to address, here's what worth evicting from a C codebase:

  • all the str* functions and char[] -- replace them with a string class
  • if you use sprintf, create a version that returns a string with the result, or puts it in the string, and replace usage. (If you never bothered with streams do yourself a favor and just skip them, unless you like them; gcc provides perfect type safety out of the box for checking formats, just add the proper attribute.
  • most malloc and free -- NOT to with new and delete, but vector, list, map and other collectons.
  • the rest of memory management (after the previous two points it must be pretty rare, cover with smart pointers or implement your special collections
  • replace all other resource usage (FILE*, mutex, lock, etc) to use RAII wrappers or classes

When you're done with that you approach the point where the codebase can be reasonably exception-safe, so you can drop return-code football using exceptions and rare try/catch in high-level functions only.

Beyond that just write new code in some healthy C++, and if some classes are born that are good replacement in existing code, pick them up.

I didn't mentions syntax-related stuff, obviously use refs instead of pointers in all new code, but replacing old C parts just for that change is no good value. Casts you must address, eliminate all you can, and use C++ variants in wrapper functions for the remainder. And very importantly, add const wherever applicable. These interleave with the earlier bullets. And consolidate your macros, and replace what you can make into enum, inline function or template.

I suggest reading Sutter/Alexandrescu's C++ Coding Standards if not yet done and follow them closely.

1
  • Thanks a lot for the input Balog, all sound advice in my eyes and I agree with all of it. I will look to slowly change the code section by section I think, prioritising working code.
    – Baldrick
    Jun 18, 2013 at 20:52
7

In short: you're not going to hell, nothing bad will happen, but you're not going to win any beauty competitions either.

While it is perfectly possible to use C++ as a "better C", you're throwing away many of C++'s benefits, but because you're not restricting yourself to vanilla C, you're not getting any of C's benefits (simplicity, portability, transparency, interoperability) either. In other words: C++ sacrifices some of C's qualities to gain others - for example, C's transparency where you can always see clearly where and when memory allocations happen is traded for C++'s more powerful abstractions.

Since your code seems to work OK now, it's probably not a good idea to rewrite it just for the sake of it: keep it as it is for now, and change it to more idiomatic C++ as you go, one piece at a time, whenever you work on any given part. And keep the lessons thus learned in mind for your next project, most of all this one: C and C++ are not the same language, and you're better off making a choice up-front than deciding to switch to C++ halfway through your project.

1
  • Thanks tdammers for the advise. I agree with what you are saying and I'm taking it on board, thanks!
    – Baldrick
    Jun 18, 2013 at 20:55
4

C++ is a very complex language, in the sense that it has a lot of features. It is also a language that supports multiple paradigms, meaning that it allows you to write entirely procedural code without using any objects.

So because C++ is so complex, you often see people use some limited subset of its features in their code. Beginners often just use the stream I/O, the string objects, and new/delete instead of malloc/free, and maybe references instead of pointers. As you learn about the object-oriented features, you may start writing in a style called "C with classes". Eventually, as you learn more about C++, you start using templates, RAII, STL, smart pointers, etc.

The point I am trying to make is that learning C++ is a process that takes time. Yes, right now your code probably looks like it was written by a C programmer trying to write C++. And since you are just learning, that's perfectly ok. However, it does make me cringe, when I see this kind of thing in production code written by seasoned programmers who should know better.

Just remember, a good Fortran programmer can write good Fortran code in any language. :)

2

It sounds you are recapitulating exactly the path a lot of old C programmers took when going to C++. You're using it as a "better C". Twenty years ago this made some sense, but at this point there are so many more powerful constructs in C++ (like the Standard Template Library) that it rarely makes sense now. At a minimum, you likely should do the following to avoid giving C++ programmers aneurysms when looking at your code:

  • Use streams instead of the ?printf family.
  • Use new instead of malloc.
  • Use the STL container classes for all data structures.
  • Use std::string instead of char* wherever possible
  • Understand and use RAII

If your program is short (and ~900 lines seems short) I personally don't think trying to create a robust set of classes is required or even useful.

1
  • Thanks for the advice Steven, Yes I had the same idea about classes and I think I can tidy the code into one neat single file. Thanks!
    – Baldrick
    Jun 18, 2013 at 20:48
2

The accepted answer mentions only the pros of converting C to idiomatic C++, as if C++ code would be in some absolute sense better than C code. I agree with other answers that it's probably unnecessary to do any drastic changes to the mixed code if it isn't broken.

Whether mixing C and C++ is sustainable depends on how the mixing is done. Subtle bugs can be introduced for example when exceptions are raised in C-code (which isn't exception safe), causing memory leaks or data corruptions. Safer and quite common way is to wrap C-libraries or interfaces in classes, or use them in some other kind of isolation in a C++ project.

Before you decide to rewrite your whole project to idiomatic C++ (or C), you should be aware that many of the changes presented in other answers can make your program slower, or cause other unwanted effects. For example:

  • changing stack-allocated C-strings to std::strings can cause unnecessary heap allocations
  • changing raw pointers to some shared pointer types (like std::shared_ptr) causes access overhead due to reference counting, internal virtual member functions and thread safety
  • standard library streams are slower than C counterparts
  • careless usage of RAII classes, like containers, can cause unnecessary operations, especially when move semantics of C++11 can't be used
  • templates can cause longer compile times, unclear compile errors, code bloat and portability issues between compilers
  • writing exception-safe code is difficult, which makes introducing subtle bugs during the conversion easy
  • it's tricker to use C++ code from a shared library than plain C
  • C++ is not as widely supported as e.g. C89

To conclude, converting from mixed C and C++ code to idiomatic C++ should be seen as a tradeoff which has a lot more to it than just implementation time and enlargening your toolbox with seemingly convenient features. Therefore it's very hard to give an answer for the general case other than "it depends".

1
  • "standard library streams are slower than C counterparts": Likely, certainly harder for internationalization than printf anyway. Jun 28, 2015 at 13:29
1

It is bad form to mix C and C++. They are distinct languages and really should be treated as such. Pick whichever one you are most comfortable with and try to write idiomatic code in that language.

If C++ is saving you a lot of code, then stick with C++ and re-write the C parts. I doubt a performance difference will even be noticeable, if that's what you're concerned about.

1
  • So I have this one library that I want to use which is written in C, and I have this other library that I want to use which is written in C++... Rewriting code that works just fine is just creating unnecessary work.
    – gnasher729
    Dec 17, 2017 at 13:44
1

I go back and forth between C and C++ all the time and am the oddball type that prefers working this way. I prefer C++ for higher-level things while I use C as a blunt instrument that allows me to x-ray data types and treat them like bits and bytes with, say, memcpy which I find handy for implementing low-level data structures and memory allocators. If you're finding yourself working at the level of raw bits and bytes, then C++'s really rich type system doesn't help anything at all, and I often find it easier to write such code in C where I can safely assume that I can treat any C data type as just bits and bytes.

The main thing to keep in mind is where these two languages do not mesh well.

1. A C function should never call a C++ function which can throw. Given how many places your daily kind of C++ code can implicitly run into an exception, that generally means your C functions should not be calling C++ functions in general. Otherwise your C code will be unable to free resources it allocates during stack unwind since it has no practical way to catch the C++ exception. If you do end up requiring your C code to call a C++ function as a callback, e.g., then the C++ side should ensure that any exception it encounters is caught before returning back to the C code.

2. If you write C code that treats data types as just raw bits and bytes, bulldozing over the type system (this is actually my main reason for using C in the first place), then you never want to use such code against C++ data types which could have copy constructors and destructors and virtual pointers and things of that sort which need to be respected. So generally it should be C code using C code of this sort, not C++ code using C code of this sort.

If you want your C++ code to use such C code, then typically you want to use it as the implementation detail of a class which makes certain that the data it is storing in the generic C data structure is a data type which is trivial to construct and destroy. Fortunately there are type traits like this which you can use to check that in a static assert, making sure that the types you are storing into the generic C data structure have trivial destructors and constructors and will stay that way.

Should I look to rewrite it all in C++ (by this, I mean implement more C++ objects and methods where perhaps I have coded something in a C style that can be condensed using newer C++ techniques), or remove the use of string streamer objects and bring it all "back" to C code?

In my opinion you need not bother if you adhere to the rules above and make sure your code is well-tested against tests you've written. The part that might be worth improving is the code you've written in C++ so far, but that doesn't mean you have to port the code that is strictly C-based to C++.

With the code you have written in C++ and compile as C++ code, there you have to be careful. Using C-like coding in C++ is asking for trouble. For example, if you allocate and free resources manually, chances are that your code isn't exception-safe since your code might encounter an exception at which point you implicitly exit from the function before you can ever free the resource. In C++, RAII and things like smart pointers aren't a fluffy convenience as they might appear if you only looked at normal execution paths without considering exceptional paths. They're often a fundamental necessity to write correct exception-safe code simply and effectively that won't just start leaking all over the place when encountering an exception.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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