10 small things that make C ++ easier
01/29/2019
Author / editor: Dominik Berner* / Sebastian Gerstl
C ++ has noticeably modernized the introduction of the standards C ++ 11/14/17.In addition to language features such as smart points, Move Semantics and Varaidic Template from, there are also a lot of smaller extensions that often fly under the radar.But it is precisely these features that can help to simplify C ++ code noticeably and to make it more waiting.
Companies on the subject
BBV Software Services AG
The "big" changes such as Variadic Template from, car, Move semantics, Lambda expressions and others have provided a lot of discussion material and are therefore known far around.In addition to the language features, the standard library has also experienced a noticeable expansion and many concepts from libraries such as Boost have been standardized.In addition to this very noticeable (and sometimes also controversial) features, there are a lot of small - but - fine language extensions, which are often less known or are overlooked.
Precisely because these features are often very small and sometimes almost invisible, they have great potential to make life easier in everyday programming life and gently modernize code without serious interventions.It is often the case that when working with existing code, you do not have the opportunity to make large structural or visible changes from the outside, but this is exactly where the "small features" can help to keep code up to date and waiting.
Modern, waiting code
Maintenance, readability and code quality are topics that have become an integral part of today's software development. The advantage of software compared to hardware is that it can be adapted and revised relatively easily and, especially where Agil is worked, this often happens very consciously and again and again. With this volatility, these quality features gain even more weight, because poor code gives the advantage of simple processing more than destruction. Things like Clean Code, the Solid Prince or Paradigms such as Low Coupling, Strong Cohesion are important aspects of code quality, but quality begins when using the language itself. The use of the language features provided. Code to clarify and often also make it easier to automatically verify these intentions. In addition, the amount of written code can often be reduced, which plays the principle of "Less Code Means Less Bugs".
An example of illustration
A simple algorithm can be very complicated if the spelling does not meet expectations or the author came up with a particularly clever hack for optimization.For example, the exchange of two variables X and Y can be written as follows:
X = X ^ y; y = y ^ x; x = x ^ y;
This XOR swap is storage-efficient and has its right to exist in very specific cases, but the operation is not intuitive.Even with a code comment, this simple example forces the reader unnecessary thinking.The following example reads much easier:
STD :: Swap (X, y);
Check inheritance with override and final
Inheritance is equally curse and blessing for many programmers.On the one hand, it often helps to avoid code duplication, on the other hand - especially in C ++ - there are many stumbling blocks that must be observed.Especially with refactoring on basic classes, it always happens that the dependent classes are forgotten and you only notice this at the term.The keyword override has been providing a remedy here since C ++ 11.Override should be used whenever a function is overwritten in a inheritance tree.This automatically becomes virtual and the compiler gets the possibility to check whether a method is actually overwritten and whether the overwritten method is actually virtual.
Struct base {virtual int func () {return 1;}};Struct Derived: Public Base {// Compiler-Reror if base :: Func does not exist or is not virtual int func () override {return 2;};};
You get even more control over the inheritance tree if you can completely prevent the inheritance from a certain point.The Final specificator indicates that a class or virtual function cannot be overwritten.This does not reduce the writing effort, but clearly communicates an intention behind a piece of code, namely that no further inheritance is desirable.Even the compiler helps with the compilation failed, you should try this.
Class Base Final {};Class Derived: Public Base {} // Compiler Error Class Base {:::: virtual void f ();};Class derived: Public Base {// f Cannot be overrids by Further Base Classes Void f () Override Final;};
Using declarations and constructors inheritance
Duplating code is a horror for the programmer, even if this is generated code.Using declarations allow the programmer to "import" a symbol of a declarative region, such as name rooms, classes and structures into another without generating additional code.In classes, this is particularly useful to take over constructors from basic classes directly, without all the variants having to be rewritten.Another example is to explicitly design implementations in derived classes.This clearly signals to the reader that a “foreign” implementation is used here that has not experienced any functional modification.
Struct a {a () {} explicit a (char c {} int get_x (); int func ();} Struct B: public a {using a :: a; // get all constructors from a using a :: Func; int func (int); // Could Possibly mask a :: Func () private: Using A :: get_x; //}
This has been working for classes and structures for a long time;Since C ++ 17, taking over symbols has also been working for (nested) name rooms:
void f () {} namespace x {void x () {};void y () {};void z () {};} Namespace I :: K :: L} Using :: f;// f () is Available in I :: K :: L now using x :: x;// x () is available in i :: k :: l}
Forwarding of constructors
Other high-level programming languages have known the "chain" of constructors for a long time and since C ++ 11 this has also been possible in C ++.The advantages of less duplicated code and thus easier readability and thus better maintainability are obvious.Especially with constructors, the internally complicated initials and/or checks do this helps a lot and promotes the implementation of the Raii (Resource Allocation is initialization) paradigm.
Class DelegatingCtor {int Number_;Public: DelegatingCtor (int n): Number_ (n) {} delegattingctor (): delegatingtor (42) {};}
In connection with the use of the constructor inheritance with Using mentioned above, code can be further compressed.
Class base {public: base (int X): x_ {x} {};Private: Int X_;};Class Derived: Public Base {Public: Using Base :: Base;// imports base (int) as derived (int) derived (char): derived (123) {} // delegating ctor:};
= Delete - deletion of functions
Less code means fewer bugs, even with code generated.So we make it easier for the compiler to generate the work of code that we do not want and need.The Keyword Delete for function declaration - not to be confused with the corresponding expression to delete objects - is another very strong extension in C ++ 11, with which a programmer can not only signal an intention, but can also be enforced by the compiler.The use of = delete can explicitly ensure that certain operations such as copying an object are not intended and possible.Of course, the "Rule of Five" should also be observed when deleting functions.
struct NonCopyable { NonCopyable() = default; // disables copying the object through construction NonCopyable(const Dummy &) = delete; // disables copying the object through assignement Noncopyable &operator=(const Dummy &rhs) = delete; }; struct NonDefaultConstructible { // this struct can only be constructed through a move or copy NonDefaultConstructible() = delete; };
Guaranteed preventing copies
The guaranteed prevention of copies (English Guaranteed Copy Elision) is usually invisible to the programmer, but behind it is great potential for smaller and cleaner code.This repayment prevents unnecessary copies of temporary objects if they are assigned to a new symbol immediately after creating.Some compilers, such as GCC, have been supporting this for a long time, but with C ++ 17, leaving copies as guaranteed behavior was included in the standard.In addition to the effect that less code is generated, it leaves the programmer to implement his intention that an object must not be copied or postponed with even greater consequence.Using the above = Delete, this can be expressed very clearly.
class A { public: A() = default; A(const A &) = delete; A(const A &&) = delete; A& operator=(const A&) = delete; A& operator=(A&&) = delete; ~A() = default; }; // Without elosion this is illegal, as it performs a copy/move //of A which has deleted copy/move ctors A f() { return A{}; } int main() { // OK, because of copy elision. Copy/move constructing // an anonymous A is not necessary A a = f(); }
Structured Bindings
Classes and structures are not the only way to structure the handling of data.The standard library also provides a whole lot of data containers for exactly these purposes.With std :: tufle and std :: array, two data structures were introduced in C ++ 11 with the size of the known size.While Std :: Array is a relatively simple modernization of C-arrays with std :: tuble a generic possibility was created to conveniently hand around heterogeneous data in the program without the programming having to create pure data classes or structures.
Since C ++ 17, access to the content of these data structures has been very lightweight due to the structured bindings:
auto tuple = std: :make_tuple<
1, 'a', 2.3>; const auto [a, b, c] = tuple; auto & [i, k, l] = tuple;
It should be noted that all variables have the same constant ness here and either read as a reference or by-value.The Structured Bindings also work in connection with classes, but this is somewhat problematic, since the semantics of classes of classes do not provide for a strong order of the member.There are ways to re -play this semantics, but this is comparatively complex.
Strongly typed enums
One of the most frequently used options for creating your own data types with clear values were already in C the enums and even today they are still used often.A often cited annoyance is that the type safety is insufficiently ensured when using enums.In the past, it was possible to assign a value of an enum type of a variable of another enum type.With the new standards, this is a thing of the past when using the past.If an enum definition is added to the keyword class or struct, a strongly typed data type and use with another enum type leads to a warning or error when compiling.As an additional bonus, so to speak, the underlying data type for an enum has been explicitly specified since C ++ 11, which benefits the portability of the code.
enum color: uint8_t {red, green, blue};enum class sound {boing, gloop, crack};Auto s = sound :: Boing;
Time literals with
A very frequent use of data with clear but not always linear values is of course the time itself, especially for applications with strict time requirements. Handling of time units is a nightmare for many programmers.The reasons are diverse, from the non-linear division of seconds, minutes and hours to the fact that confusion quickly arises, what time unit is a call like Sleep (100).Is it seconds?Milliseconds?With the introduction of std :: Chrono in C ++ 11 and adding time literals, handling becomes a lot easier.With the liters, times with a simple suffix in the code with a fixed unit or with a fixed resolution can be declared.
Delivers everything between microseconds and hours.By using the time units supplied by StD :: Chrono, time values can already be converted to the compile-time and the annoying manual conversion at the time is part of the past.
using namespace std: :chrono_literals; auto seconds = 10s; auto very_small = 1us; // automatic, compile-time conversion if (very_small < seconds) { ... }
Reviews with initialization
At first glance, the introduction of direct initialization in If and Switch statements in C ++ 17 is a way a little bit more compact.Another somewhat hidden advantage is that the programmer can only express his intention that a symbol is only used within a branch.The initialization of having or in the condition right from or in the condition also prevents the risk of being separated from the branch in refactoring.
if (int i = std :: rim (); i % 2 == 0) {} Switch (int i = std :: rim (); i = % 3) {case 0: ... case 1: ... Case 2: ...}
In connection with the above -mentioned structured binding, direct initialization can be used very elegantly.In the following example, an attempt is made to overwrite an existing value in a std :: MAP.The return value of insert becomes an iterator and the flag, whether the operation was successful and can therefore be used directly within the query.
std: :map<
char, int> map; map[ 'a' ] = 123; if (auto [it, inserted] = map.insert ({ 'a', 1000}); !inserted) { std: :cout <<
"'a' already exists with value " << it->second <<
"\ n";}
Standard attributes
Wann immer ein Programmierer eine Annahme trifft, sollte dies im Code dokumentiert sein. Mit den Standard attributesn können einige solcher Annahmen mit wenig Aufwand dokumentiert werden. Attribute sind seit längerem für verschiedene Compiler bekannt, allerdings war die Notation für die verschiedenen Compiler oft unterschiedlich. Seit C++17 wurde diese als [[ attribute ]] standardisiert was portablen den Code lesbarer macht. Zudem wurden verschiedene von allen Compilern unterstützte Standard attributes eingefügt, welche es dem Programmierer erlauben seine Absichten für gewisse Konstrukte explizit zu formulieren
[[Noreturn]] indicates that a function does not return, e.g. because it always throws an exception
[[Deprecated]] [[Deprecated ("Reason")] indicates that the use of this class, function or variable is allowed, but is no longer recommended
[[Fallthrough]] Used in Switch Statement to indicate that a case: block does not contain any break on purpose
[[NODISCARD]] produces a compiler warning if such a marked return value is not used
[[maybe_unused]] Unterdrückt Compiler-Warnungen bei nicht verwendeten Variablen. z.B. in Debug-Code <
Conclusion
These 10 small features and functions are of course only a small part of what makes modern C ++.But through their consistent application, code can be made understandable with relatively little effort and easier to understand without the complete structure of an existing code base.
The author
*Dominik Berner ist ein Senior Software-Ingenieur bei der BBV Software Services AG mit einer Leidenschaft für modernes C++. Die Wartbarkeit von Code ist für ihn kein Nebeneffekt, sondern ein primäres Qualitätsmerkmal, das für die Entwicklung von langlebiger Software unabdingbar ist.
(This post was taken from the author's embedded software engineering congress 2018 with the kind permission of the author.)
Article files and article links
Link: Embedded Software Engineering Congress
(ID: 45706431)