The differences between C and C++ are so large these days that they are two different languages that require differences in how designs are expressed in those languages.
C offers one paradigm, procedural, for writing code where as C++ is multi-paradigm allowing a larger implementation vocabulary for implementing a design. You can use a procedural paradigm or a generative paradigm with templates or object oriented paradigm with classes or a functional paradigm with support from the Standard Template Library.
This difference in supported paradigms means that a C programmer often has to write C code in a procedural paradigm when C++ would offer a better and simpler alternative. The C programmer has to know how to translate from the abstract solution domain which may involve non-procedural concepts into the concrete solution domain within the constraints of what the C programming language offers.
A knowledgeable and skilled and experienced C programmer is quicker at this transformation and more inclined to use accepted practices and expressions. A knowledgeable and skilled and experienced C programmer is better able to read existing source code with understanding and to make changes that have less chance of introducing a defect.
Over the years I have learned from others or developed or found techniques that overcome some of the limitations that C has for large (as in greater than a million lines of source) bodies of source code. However doing so requires knowing C very well and having the experience with the language to work around its deficiencies and experience with other languages to know of those deficiencies in the first place. And often those workarounds provide opportunities for introducing defects by removing compile time checking such as using
void * in argument lists.
The first thing to remember is that while the C++ standards committee has made great leaps of innovation in C++ between the original ANSI C++ to C++11 to C++17 to C++20, the C standards committee has made small changes.
The result is that the kind of well designed and organized standard libraries and capabilities available with C++17 require C programmers to cobble together a collection of third party libraries. And C++20 is coming.
The C programming language was not really designed for huge, multi-million lines of source code projects where as C++ is. C was used to write the UNIX operating system yet according to Wikipedia, Unix - Components,
The inclusion of these components did not make the system large – the
original V7 UNIX distribution, consisting of copies of all of the
compiled binaries plus all of the source code and documentation
occupied less than 10 MB and arrived on a single nine-track magnetic
tape. The printed documentation, typeset from the online sources, was
contained in two volumes.
namespace directive was added to C++ in order to meet the need of large bodies of source code to be manageable by partitioning out domains for names of classes, types, functions, etc. I've used
struct with function pointers and a global variable as a kind of
namespace approach for functions but you still can run into name space collisions with types and definitions. This is why the three letter subsystem acronym prefix naming convention is used with large C source code bodies.
C requires much more attention to detail than does modern C++. You can write modern C++ without using pointers and when you do use pointers, you have facilities that make pointers safer than what C offers. The result is that writing large bodies of source code in modern C++ can be much safer than writing in C and the error checking at compile time is better with C++ because the type system is more specific and less loose.
C++ offers more modern error handling than C with exceptions. Using exceptions can make error recovery easier and the use of object destructors allows for a more elegant and simpler cleanup in the face of errors. And you aren't required to use exceptions in those places where they don't make sense.
Encapsulation is easier and more complete with C++ classes and namespaces which leads to source code with better cohesion and less chance for defects.
Class constructors and destructors of C++ provide a capability for a defined starting and ending state that C doesn't have. And the ability to redefine operators allows for the development of true types that provide a straightforward and intuitive expressiveness of C++ source code that C lacks, a lack which requires work arounds and makes more cognitive demands on the C programmer.
Templates in C++ provide an immense power lacking in C and the C Preprocessor is in no way comparable to the capabilities of templates. The C Preprocessor is a separate component, a text processor that parses a file looking for text that looks to be a Preprocessor directive generating text which may or may not be C source code. This means that the kind of checking the C++ compiler does with templates is not available with the C Preprocessor and it also means that information available for writing templates is not available to
define and Preprocessor macros.
A C programmer will have spent much more time with the Preprocessor and its idiosyncrasies than a modern C++ programmer who will rely on the more powerful templates instead.
The C++ Standard Library and Standard Template Library makes the C Standard Library look like a barely functional, crippled library.
Text string processing in C++ is so much easier and safer than C.
C++17 multi-threading support is much better than what C11 offers.