We happen to write code for IoT stuff related to electricity, like car charging stations. Since hardware resources are usually sufficient, our main goal is not to save on every byte possible. Our main goal is safe and readable code. That's why our project developers work on Embedded Linux. They also use C++ in its modern version (C++17) as a main language and can't stop thinking about the new features from C++20 and newer (wait, did somebody say Rust?).
We published and translated this article with the copyright holder's permission. The author is Kirill Ovchinnikov (kirill.ovchinn@gmail.com). The article was originally published on Habr.
Sometimes we need to ship new projects on the same platform with the same processes, and we reuse many existing components. In this case, we are looking for new developers, more precisely — C++ developers. However, pure C is still in demand for embedded systems, and C developers are the ones who try for C++ developer positions. The logic is simple: the languages are quite similar and almost backward-compatible; the base syntax is the same. Besides, if the developers heard something about OOP, then they think they know the base and can easily learn C++ in 21 day. That's why they BS their way through the interview with "Yeah, I worked with C++". They hope to start writing on "C with classes" and succeed. But the new team already has a couple of such ex-C developers. What we need is a hardcore C++ developer who would cheerfully implement best practices and set junior teammates on the path of righteousness during code review.
Yes, C and C++ languages look similar. However, the more you know about these languages, the more you realize that they differ. As a result, you can easily distinguish a C developer from a C++ developer during an interview or review. Our team has created a list of signs that give out a C developer trying for a C++ developer position. If we see such signs, we start a more serious conversation like "why did you write the code this way?" So, here are the signs that you're talking to a developer who writes code on "C with classes":
1. Uses <stdint.h>, <string.h>, <stdio.h> instead of <cstdint>, <cstring>, <cstdio>;
2. Uses malloc() and free() except for designated places (like custom allocators);
3. Uses manual memory management with new and delete, instead of RAII and smart pointers;
4. Uses char* strings and <string.h> functions instead of std::string and std::string_view (the only exception is string constants via constexpr). Uses functions from <time.h> instead of std::chrono. Uses atoi() instead of stoi(). Uses functions from <stdio.h> instead of std::filesystem and IO streams. Uses <pthread.h> instead of std::thread;
5. Uses #define macros or void* pointers instead of templates. C developers use them when it is necessary to implement an algorithm or container independent of the data type it operates with;
6. Uses #define instead of const and constexpr to declare constants;
7. Uses C-style arrays instead of std::array;
8. Uses NULL instead of nullptr;
9. Uses (type)something instead of static_cast<type>(something);
10. Uses simple pointers to functions instead of std::function;
11. Uses enum instead of enum class even for simple enumerations;
12. Does not use const when declaring a function that does not change the state of objects. Forgets explicit for constructors; forgets virtual for destructors :)
13. Declares all class members as public when developing in OOP style;
14. If they need to return several different values from a function (for example, the result of work and/or an error code), then they return one of them via return, and the other by pointer or by a non-constant reference – instead of using std::optional, std::pair/std::tuple (especially good when paired with structured binding) or simply returning struct;
15. Always writes struct in the type name when declaring a new variable with a struct type. Or vice versa, when declaring a new structure writes typedef struct instead of just struct;
16. Does not use namespaces when structuring code;
17. Uses union instead of std::variant (by the way, you can't use union for a typing pun either, it violates the active member rule);
18. Writes implementations of commonly used algorithms (foreach, transform, find_if, sort, lower_bound, etc.) manually even if they are in <algorithm>;
19. Writes verbose constructions instead of range-based for during a simple iteration through the elements of the container; does not use auto and using in verbose type constructions;
A few additions from the comments:
20. Uses bit fields instead of std::bitset;
21. Uses C libraries directly without an abstraction layer above it;
22. Has a lot of includes in the header files that could be avoided (incomplete class).
If you are a hardcore C++ developer, and when reading this list, you have a burning disagreement with some of these points — that's great, then you really are a hardcore C++ developer. And for the rest of you, perhaps, I'll note that there are exceptions for many of the described situations. Everything depends on the specific situation. For example:
After all, these are special cases. If a person can competently justify the use or non-use of a particular language construct or API, then this already speaks about his skills and should be taken as a plus.
0