C++ if statement alternatives - c++

Is it me, or does it seem like C++ asks for more use of the 'if' statement then C#?
I have this codebase and it contains lots of things such as this:
if (strcmp((char*)type,"double")==0)
I wondered isn't it a bit of a 'code smell' when there's just too many if statements?
I'm not saying there bad, but things like string comparisons, with lots of strings involved, can't they be done differently?
Is there an alternative to just writing sequences of if statements?
THIS IS JUST AN EXAMPLE, IT CAN BE ANY KIND OF IF STATEMENTS
instead of:
if (string a == "blah") then bla
if (string b == "blah") then blo

The reason you do if (strcmp((char*)type,"double")==0) is because you can't make "double" a case-expression and use a switch statement. That said, if you're doing a lot of these kinds of string matches, you may want to look at using a std::map<std::string, int> or something similar and then use the map to convert the string to an index which you then feed to switch.
Personally, in these cases, I'm a fan of things like std::map<std::string, int (Handler::*)(void)>, which lets me create a handler map of class methods, but YMMV.
EDIT: I forgot to mention: the other sweet thing about having a map of strings to methods is that you can alter (usually add to) it at run time. For example, a parser could change its list of keywords and their handlers at runtime after it knows what kind of file it's parsing.

This is code smell.
To minimize it, you should (in this case) use std::strings. Your code then becomes:
#include <string>
// [...]
std::string type = "whatever";
// [...]
if (type == "double")
This is almost identical to the C# equivalent: to compile this sample code in C# code just remove the include and the std::.
Usually, if you find code that uses char* directly in C++ it's usually doing it wrong (except maybe for some rare exceptions).
Edit: Mike DeSimone addressed the problem of further refactoring this in his answer (so I won't mention it here :) ).

I don't think C++ requires any more "ifs" than C#. The number of if statements in a program is really just a matter of coding style. You can always eliminate ifs through techniques like polymorphism, table driven methods, and so on. These same techniques are available in both C++ and C#. If there is a difference between programs written in these two languages, I suspect it has to do with the mentality of C# vs C++ programmers.
Note that I don't necessarily recommend "if" elimination. In my experience, if statements tend to be clearer than the alternatives. To directly address your second point: the way to eliminate chained string comparisons like that is to use a DFSA. Most of the time, however, string comparisons are perfectly suitable.

It's not something I've noticed; I've done 10 years C++ and 4 years of C# too!
Surely the number of if's relates to the design of your code rather than a difference between C# and C++?

To get rid of conditional expressions in either language you can consider the Inversion of Control pattern. It has the side effect of lessening those.

Based on the nature of 'bla' and 'blo' you can always try to use a std::map, with the strings as keys.

Too many if statement are code smell if you can replace them by a switch...case. Otherwise, I don't see the problem with using if.
Maybe you have used more event-driven programming in C#, while your C++ code is more sequential ?

There are better ways to implement a string parser than an endless set of if (strcmp...) statements.
One approach could be a map between strings and function pointers or functor objects.
Another design could involve a chain of responsibility pattern where the string is passed to a chain of objects that decide if they have a match or to pass it along.
I'm not aware of anything about C++ that makes it more prone to "if abuse" than any other language.

Related

Creating "shortcuts" macro-like definitions

In my code I've been rewriting static_cast<int *> about a million times, is there a way to redefine a keyword so that whenever I call this it does the same thing?
example
cast would do the same thing as static_cast<int *>
static_cast has the benefit that C++ programmers will recognize exactly what it is without needing to go find your #define or other statement. I would highly recommend you continue to use static_cast.
However, my assumption is that your problem is the number of keystrokes required, and so the best solution would be to use a text editor which supports macros. This way, the code that ends up saved does use the standard static_cast<T>(x) syntax, but you may only need to type something such as [sc]tabTtabxtab.
Information on how to do such will be found in documentation of such editors. I'm not a big fan of highly-customizable editors, so specifics are beyond my knowledge.
Asking for easier way to do something dangerous…
Yes, there are a lot of ways to accomplish what you ask for, including
C++ template,
macro definition,
editor shortcut,
custom preprocessing,
trained monkey that fixes up the code.
But all you accomplish is to make your code even less grokable.
Instead, try to figure out how come you so often lose type information so that you have to put it back by hand, so to speak.
The general solution is, very simply, to not throw away type information in the first place.
You mean like this?
#define SCAST(T,X) static_cast<T>(X);
I should warn you though that generally the overuse of defines like this can make your code obscure and harder to comprehend.
More importantly you have to watch out with macros as they can cause hard to find bugs for example:
#define SQUARE(X) = X*X;
Well if you call this with x++, the pre-processor will do a literal substitution and you'll end up with (x++)*(x++); which means it totally won't be the answer you're looking for and to make things worst because the substitution happens behind the scenes you'll have a hard time finding the cause.
I would suggest you instead look into template functions or just inline helper functions when you can, it's safe and will avoid the problem I pointed out.

The named loop idiom : dangerous?

I've read an article about the "Named Loop Idiom" in C++ : http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Named_Loop
This idiom allows us to write things like that :
named(outer)
for(int i = 0 ; i < rows ; ++i) {
named(inner)
for(int j = 0 ; j < cols ; ++j) {
if(some_condition)
break(outer); // exit the 'outer' loop
}
}
Such constructs already exists as core feature in many languages, like Java for instance.
According to the article, it can be implemented in C++ by defining two evil macros :
#define named(blockname) goto blockname; \
blockname##_skip: if (0) \
blockname:
#define break(blockname) goto blockname##_skip;
I know that many people would like to banish the use of goto. I personally found it helpful in very rare cases, especially when I wanted to break a bunch of nested loops. This idiom appears to me as a cleaner solution for that, but is it ok to use it in real code ?
On the discussion page of the article, one can read :
"Do not do this. You'll end up in hell"
So my questions are : What are the drawbacks of using the named loop idiom ? Is it dangerous ? If yes, why ?
Bonus question : is it possible to implement named continue similarly ? (I think it's not possible using the named(...) for(...;...;...) {} syntax, but who knows ?)
EDIT : I agree with you, redefining a keyword is nasty. What about using #define breakLoop() instead?
As covered in the comments, #defining break is problematic. Let's assume you use something else.
I'd still argue that this is dangerous. It's an extremely unusual idiom (to C++ programmers), so they're less likely to understand, and thus they might make breaking changes. Given that there are less-surprising--and therefore less-dangerous--ways to accomplish the same thing, I would advise against it.
Consider putting the loops in a function or a lambda. Then you can return to break out of the outer loop. As a benefit, you can return information about the premature exit, which may be useful to the outer code.
I find a couple of problems with this.
First, you're defining a macro with the same name as one of the language's reserved words. Even if your compiler doesn't gripe about that, it's error-prone and not and (IMO, at least) dangerous.
Second, I'm always hesitant to create labels programmatically. Even though your compiler will probably complain if you accidentally create two labels with the same name in the same scope, the error message it generates will probably not be easily understood without the programmer dissecting these macros (which partially defeats the purpose of the extra abstraction).
Probably my main problem is that the macros introduce something that is unlike anything in the normal language syntax. The named(...) lines don't end in semicolons nor are they followed by a { ... } block. Adding any sort of new syntax opens the door for developer confusion and accidental misuse.
Overall, I kind of like the idea of named loops, but this isn't the sort of thing that you'd want to create using macros. It's a mechanism that would really need to be provided by the language itself. When using C or C++, it's cleaner, safer, and more maintainable to use a manually-created label and a goto. It's almost always better to be explicit than to hide what's going on behind macros.

C-like procedures in C++?

Does the C++ correct programming style demand writing all your code with classes or are C-like procedures allowed ? If I were to give some code to someone else, would it be accepted as C++ just because it has std::vector and std::string (instead of char *) inside, or everything has to be a class?
eg:
int number = 204;
std::string result = my_procedure(number);
OR
MyClass machine;
std::string result = machine.get(number);
Are there cases where the programmer, will have to, or is allowed to have C-like procedures in some of his source code ? Did you ever had to do something like that?
In the context of this question where does the margin between C and C++ exist (if any)?
I hope my question is clear and inline with the rules.
It's certainly OK to have free functions in your code -- this is a matter of architecture, not of "++ness". For small programs it doesn't even make sense to go all-in with classes, as OO is really a tool to manage complexity. If the complexity isn't there to begin with, why bother?
Your second question, where is the line drawn, doesn't have a short answer. The obvious one is that the line is drawn in all places where the C standard differs from the one for C++. But if you are looking for a list of high-level language features that C++ has and C does not, here are some of them:
Class types and OO (of course)
The STL
Function/operator overloading
References
Templates
new/delete to manage memory
C++ is a multi-paradigm language, where OO, procedural, generic/generative and - to a lesser (but increasing with C++0x) extent functional - are among the paradigms. You should use whichever is the best fit for the problem: you want the code to be easy to get and keep right, and hard to stuff up.
The utility of classes is in packaging data (state) along with the related functions. If your wordify function doesn't need to retain any state between calls, then there's no need to use a class/object. That said, if you can predict that you will soon want to have state, then it may be useful to start with a class so that the client code doesn't need to change as much.
For example, imagine adding a parameter to the function to specify whether the output should be "first", "second" instead of "one", "two". You want the behaviour to be set once and remembered, but somewhere else in the application some other code may also use the functionality but prefer the other setting. It's a good idea to use an object to hold the state and arrange it so each object's lifetime and accessibility aligns with the code that will use it.
EDIT:
In the context of this question where does the margin between C and C++ exist (if any)?
C++ just gives you a richer set of ways to tackle your programming tasks, each with their necessary pros and cons. There are plenty of times when the best way is still the same way it would have been done in C. It would be perverse for a C++ programmer to choose a worse way simply because it was only possible in C++. Still, such choices exist at myriad levels, so it's common to have say a non-[class-]member function that takes a const std::string& parameter, combining the procedural function call with object-oriented data that's been generated by a template: it all works well together.
C++ allows a variety of programming styles, procedural code being one of them.
Which style to use depends on the problem you are trying to solve. The margin between C and C++ is are you compiling your code with a C++ compiler.
I do at times use procedural functions in my code. Sometimes it best solves the problem.
C++ code can still be valid C++ code even without classes. Classes are more of a feature, and are not required in every piece of code.
C++ is basically C with more features, so there isn't really a "margin" between the two languages.
If you read Stroustrup's Design and Evolution, you'll see that C++ was intended to support multiple programming styles. Use whichever one is most appropriate the problem (not the same as always just usnig the one you know.)
In legacy real world applications, there is often very little distinction. Some C++ code was originally C code nad then recompilied. Slowly it migrates to use C++ features to improve its quality.
In short, Yes, C++ code can be procedural. But you'll find it does differ from C code if you use C++ features where appropriate.
What is good practice needs to consider things like encapsulation, testability, and the comprehensibility of the client API.
#include <sstream>
#include <string>
#include <iostream>
using namespace std;
string wordify(int n)
{
stringstream ss;
ss << n; // put the integer into the stream
return ss.str(); // return the string
}
int main()
{
string s1 = wordify(42);
string s2 = wordify(45678);
string s3 = wordify(-99);
cout << s1 << ' ' << s2 << ' ' << s3 << '\n';
}

Strange Pattern: all functions/methods return error-code using the same type in C++

In my last two projects I've seen the strange guideline, "All Methods/Functions should return error-code using some common ERROR_CODE type". In both projects ERROR_CODE is an int typedef.
Is there any good reason doing it in C++? Some MISRA requirement or something like that?
I can see only disadvantages:
If a function should return a value, it is done by argument reference. e.g.:
string s;
ERROR_CODE err = getString(s);
The importance of a function is not obvious. All looks the same. The list of errors conntains hundreds of errors from low level errors to some domain specific errors.
Have you experienced this programming style? Are there good arguments against it or for it?
I think it's a very bad style for several reasons.
Like you've said, it forces you to pass pointers/references to store the actual result of a function.
Like you've said, the unified error code is ugly because it's trying to unify all sorts of errors from all sorts of domains.
It creates an artificial dependency of all the program's modules on the error code system, making it awkward to reuse a single module or small subset of modules in other programs.
Further, since some of the error codes are domain-specific, it's actually introducing dependencies between unrelated object types/modules, since they're all dependent upon a component that's dependent upon the union of all of their possible error types.
My view is that any function/method which has more than a small manageable number of ways it can fail is either overly complex or poorly factored, probably both.
If you really want to return error codes, I would swap things around and pass the pointer to the error code as an argument to the function, and make the actual result the return value. Then I would choose one of these two approaches for implementing the error codes:
The simple way: throw away all abstraction of the error code and simply use int with a few universal error classes.
The heavy object oriented way: Provide a pointer to an internal "error object" where the base class is very abstract and can be shared between all components without introducing any dependency, and where each component defines its own component-specific error objects if needed.
A better approach if you're using C++ would probably be using exceptions...
I've seen it.
kernel programming is that way, except when only one error is possible.
It doesn't sound like a great idea, but neither all that bad of one.
It's not unusual for teams to agree on a common means of returning errors, since this helps in creating a common 'look and feel' to the project's code, just like any other team-wide coding convention. This could help new team members to understand the overall picture quicker, and make maintenance within the team of other peoples' code a little more intuitive.
It's surprising to me that a C++ project is unifying behind errors rather than exceptions, however. There's a discussion of the pros and cons of using exceptions vs error codes here.
I guess one argument in favour of error code handling is if you are using a C-style API that leads you into this approach (cough... Win32... cough).
This idiom is quite common, especially in the C world.
Even though I don't use it myself and I think it makes more harm than good (more on that in the other answers), I do find an advantage of it: a consistent way to report unexpected errors to the call site. Something like the errno variable, but easier to use.
For instance, consider a set of functions:
int a();
std::string b();
double c();
std::list<long> d();
Each of the above functions would indicate the failure in a different way: a() could return an -1, b() an empty string, c() a 0.0 and d() an empty list. That's inconsistent and not quite intuitive. Now imagine a function, whose range covers the entire possible range of the type it returns. That's even worse.
Some APIs also do:
int x(bool* ok);
But that also pollutes each function with an additional argument.
In C, there aren't many possibilities to do in a nice way, unfortunately, if you really need to design such an API that would indicate the different types of failure. In the C++ world, however, you can just use exceptions.
I've seen the argument that when linking to a C++ library compiled by another compiler than is used to compile your binaries, exceptions might not work. While this non-working may totally be true, in actuality, even the linking process need not work (although everyone may be sticking to the standards), so, theoretically, this argument is void. In practice however, it may be (I don't have experience here, sorry), that name mangling conflicts rarely arise, alignment conflicts rarely arise, and, well, all other implementation specific stuff is widely agreed upon, except for exceptions.
Second argument I've seen is run-time performance. While stack unwinding in case of an exception is expensive, I've not yet seen a fair benchmark that compared exceptions to a realistic amount of return code checking.
In my typical C++ I use a mix. I use the slower exceptions for stuff that I really don't expect to happen frequently or code paths that are measured to be rarely executed, but return codes for stuff that is more likely to break and probably called frequently.
Throwing exceptions in a tight loop because some funny condition holds true in every iteration is not cheap (assuming the loop body handles it).

Moving from C++ to C

After a few years coding in C++, I was recently offered a job coding in C, in the embedded field.
Putting aside the question of whether it's right or wrong to dismiss C++ in the embedded field, there are some features/idioms in C++ I would miss a lot. Just to name a few:
Generic, type-safe data structures (using templates).
RAII. Especially in functions with multiple return points, e.g. not having to remember to release the mutex on each return point.
Destructors in general. I.e. you write a d'tor once for MyClass, then if a MyClass instance is a member of MyOtherClass, MyOtherClass doesn't have to explicitly deinitialize the MyClass instance - its d'tor is called automatically.
Namespaces.
What are your experiences moving from C++ to C?
What C substitutes did you find for your favorite C++ features/idioms? Did you discover any C features you wish C++ had?
Working on an embedded project, I tried working in all C once, and just couldn't stand it. It was just so verbose that it made it hard to read anything. Also, I liked the optimized-for-embedded containers I had written, which had to turn into much less safe and harder to fix #define blocks.
Code that in C++ looked like:
if(uart[0]->Send(pktQueue.Top(), sizeof(Packet)))
pktQueue.Dequeue(1);
turns into:
if(UART_uchar_SendBlock(uart[0], Queue_Packet_Top(pktQueue), sizeof(Packet)))
Queue_Packet_Dequeue(pktQueue, 1);
which many people will probably say is fine but gets ridiculous if you have to do more than a couple "method" calls in a line. Two lines of C++ would turn into five of C (due to 80-char line length limits). Both would generate the same code, so it's not like the target processor cared!
One time (back in 1995), I tried writing a lot of C for a multiprocessor data-processing program. The kind where each processor has its own memory and program. The vendor-supplied compiler was a C compiler (some kind of HighC derivative), their libraries were closed source so I couldn't use GCC to build, and their APIs were designed with the mindset that your programs would primarily be the initialize/process/terminate variety, so inter-processor communication was rudimentary at best.
I got about a month in before I gave up, found a copy of cfront, and hacked it into the makefiles so I could use C++. Cfront didn't even support templates, but the C++ code was much, much clearer.
Generic, type-safe data structures (using templates).
The closest thing C has to templates is to declare a header file with a lot of code that looks like:
TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{ /* ... */ }
then pull it in with something like:
#define TYPE Packet
#include "Queue.h"
#undef TYPE
Note that this won't work for compound types (e.g. no queues of unsigned char) unless you make a typedef first.
Oh, and remember, if this code isn't actually used anywhere, then you don't even know if it's syntactically correct.
EDIT: One more thing: you'll need to manually manage instantiation of code. If your "template" code isn't all inline functions, then you'll have to put in some control to make sure that things get instantiated only once so your linker doesn't spit out a pile of "multiple instances of Foo" errors.
To do this, you'll have to put the non-inlined stuff in an "implementation" section in your header file:
#ifdef implementation_##TYPE
/* Non-inlines, "static members", global definitions, etc. go here. */
#endif
And then, in one place in all your code per template variant, you have to:
#define TYPE Packet
#define implementation_Packet
#include "Queue.h"
#undef TYPE
Also, this implementation section needs to be outside the standard #ifndef/#define/#endif litany, because you may include the template header file in another header file, but need to instantiate afterward in a .c file.
Yep, it gets ugly fast. Which is why most C programmers don't even try.
RAII.
Especially in functions with multiple return points, e.g. not having to remember to release the mutex on each return point.
Well, forget your pretty code and get used to all your return points (except the end of the function) being gotos:
TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{
TYPE * result;
Mutex_Lock(this->lock);
if(this->head == this->tail)
{
result = 0;
goto Queue_##TYPE##_Top_exit:;
}
/* Figure out `result` for real, then fall through to... */
Queue_##TYPE##_Top_exit:
Mutex_Lock(this->lock);
return result;
}
Destructors in general.
I.e. you write a d'tor once for MyClass, then if a MyClass instance is a member of MyOtherClass, MyOtherClass doesn't have to explicitly deinitialize the MyClass instance - its d'tor is called automatically.
Object construction has to be explicitly handled the same way.
Namespaces.
That's actually a simple one to fix: just tack a prefix onto every symbol. This is the primary cause of the source bloat that I talked about earlier (since classes are implicit namespaces). The C folks have been living this, well, forever, and probably won't see what the big deal is.
YMMV
I moved from C++ to C for a different reason (some sort of allergic reaction ;) and there are only a few thing that I miss and some things that I gained. If you stick to C99, if you may, there are constructs that let you program quite nicely and safely, in particular
designated initializers (eventually
combined with macros) make
initialization of simple classes as
painless as constructors
compound literals for temporary variables
for-scope variable may help you to do scope bound resource management, in particular to ensure to unlock of mutexes or free of arrays, even under preliminary function returns
__VA_ARGS__ macros can be used to have default arguments to functions and to do code unrolling
inline functions and macros that combine well to replace (sort of) overloaded functions
The difference between C and C++ is the predictability of the code's behavior.
It is a easier to predict with great accuracy what your code will do in C, in C++ it might become a bit more difficult to come up with an exact prediction.
The predictability in C gives you better control of what your code is doing, but that also means you have to do more stuff.
In C++ you can write less code to get the same thing done, but (at leas for me) I have trouble occasionally knowing how the object code is laid out in memory and it's expected behavior.
Nothing like the STL exists for C.
There are libs available which provide similar functionality, but it isn't builtin anymore.
Think that would be one of my biggest problems... Knowing with which tool I could solve the problem, but not having the tools available in the language I have to use.
In my line of work - which is embedded, by the way - I am constantly switching back & forth between C and C++.
When I'm in C, I miss from C++:
templates (including but not limited to STL containers). I use them for things like special counters, buffer pools, etc. (built up my own library of class templates & function templates that I use in different embedded projects)
very powerful standard library
destructors, which of course make RAII possible (mutexes, interrupt disable, tracing, etc.)
access specifiers, to better enforce who can use (not see) what
I use inheritance on larger projects, and C++'s built-in support for it is much cleaner & nicer than the C "hack" of embedding the base class as the first member (not to mention automatic invocation of constructors, init. lists, etc.) but the items listed above are the ones I miss the most.
Also, probably only about a third of the embedded C++ projects I work on use exceptions, so I've become accustomed to living without them, so I don't miss them too much when I move back to C.
On the flip side, when I move back to a C project with a significant number of developers, there are whole classes of C++ problems that I'm used to explaining to people which go away. Mostly problems due to the complexity of C++, and people who think they know what's going on, but they're really at the "C with classes" part of the C++ confidence curve.
Given the choice, I'd prefer using C++ on a project, but only if the team is pretty solid on the language. Also of course assuming it's not an 8K μC project where I'm effectively writing "C" anyway.
Couple of observations
Unless you plan to use your c++ compiler to build your C (which is possible if you stick to a well define subset of C++) you will soon discover things that your compiler allows in C that would be a compile error in C++.
No more cryptic template errors (yay!)
No (language supported) object oriented programming
Pretty much the same reasons I have for using C++ or a mix of C/C++ rather than pure C. I can live without namespaces but I use them all the time if the code standard allows it. The reasons is that you can write much more compact code in C++. This is very usefull for me, I write servers in C++ which tend to crash now and then. At that point it helps a lot if the code you are looking at is short and consist. For example consider the following code:
uint32_t
ScoreList::FindHighScore(
uint32_t p_PlayerId)
{
MutexLock lock(m_Lock);
uint32_t highScore = 0;
for(int i = 0; i < m_Players.Size(); i++)
{
Player& player = m_Players[i];
if(player.m_Score > highScore)
highScore = player.m_Score;
}
return highScore;
}
In C that looks like:
uint32_t
ScoreList_getHighScore(
ScoreList* p_ScoreList)
{
uint32_t highScore = 0;
Mutex_Lock(p_ScoreList->m_Lock);
for(int i = 0; i < Array_GetSize(p_ScoreList->m_Players); i++)
{
Player* player = p_ScoreList->m_Players[i];
if(player->m_Score > highScore)
highScore = player->m_Score;
}
Mutex_UnLock(p_ScoreList->m_Lock);
return highScore;
}
Not a world of difference. One more line of code, but that tends to add up. Nomally you try your best to keep it clean and lean but sometimes you have to do something more complex. And in those situations you value your line count. One more line is one more thing to look at when you try to figure out why your broadcast network suddenly stops delivering messages.
Anyway I find that C++ allows me to do more complex things in a safe fashion.
yes! i have experienced both of these languages and what i found is C++ is more friendly language. It facilitates with more features. It is better to say that C++ is superset of C language as it provide additional features like polymorphism, interitance, operator and function overloading, user defined data types which is not really supported in C. The thousand lines of code is reduce to few lines with the help of object oriented programming that's the main reason of moving from C to C++.
I think the main problem why c++ is harder to be accepted in embedded environment is because of the lack of engineers that understand how to use c++ properly.
Yes, the same reasoning can be applied to C as well, but luckily there aren't that many pitfalls in C that can shoot yourself in the foot. C++ on the other hand, you need to know when not to use certain features in c++.
All in all, I like c++. I use that on the O/S services layer, driver, management code, etc.
But if your team doesn't have enough experience with it, it's gonna be a tough challenge.
I had experience with both. When the rest of the team wasn't ready for it, it was a total disaster. On the other hand, it was good experience.
Certainly, the desire to escape complex/messy syntax is understandable. Sometimes C can appear to be the solution. However, C++ is where the industry support is, including tooling and libraries, so that is hard to work around.
C++ has so many features today including lambdas.
A good approach is to leverage C++ itself to make your code simpler. Objects are good for isolating things under the hood so that at a higher level, the code is simpler. The core guidelines recommend concrete (simple) objects, so that approach can help.
The level of complexity is under the engineer's control. If multiple inheritance (MI) is useful in a scenario and one prefers that option, then one may use MI.
Alternatively, one can define interfaces, inherit from the interface(s), and contain implementing objects (composition/aggregation) and expose the objects through the interface using inline wrappers. The inline wrappers compile down to nothing, i.e., compile down to simple use of the internal (contained) object, yet the container object appears to have that functionality as if multiple inheritance was used.
C++ also has namespaces, so one should leverage namespaces even if coding in a C-like style.
One can use the language itself to create simpler patterns and the STL is full of examples: array, vector, map, queue, string, unique_ptr,... And one can control (to a reasonable extent) how complex their code is.
So, going back to C is not the way, nor is it necessary. One may use C++ in a C-like way, or use C++ multiple inheritance, or use any option in-between.