📜 ⬆️ ⬇️

C ++ Basic Principles: Expression and Operator Rules

Beaver!

Well, we smoothly enter the start of the second stream of the “Developer C ++” group and analyze interesting materials that have accumulated at the teacher in his free time from work and teaching time. Today we will consider (and then continue) a series of materials, where the individual paragraphs of C ++ Core Guidelines are analyzed.

Go.
')
In C ++ Core Guidelines there are a lot of rules dedicated to expressions and operators. To be precise, more than 50 rules are devoted to declarations, expressions, operators, and arithmetic expressions.



*transfer
Informative titles

The optimal length of variables


Too long: numberOfPeopleOnTheUsOlympicTeam; numberOfSeatsInTheStadium; maximumNumberOfPointsInModernOlympics numberOfPeopleOnTheUsOlympicTeam; numberOfSeatsInTheStadium; maximumNumberOfPointsInModernOlympics
Too short: n; np; ntmn; ns; nslsd; m; mp; max; points n; np; ntmn; ns; nslsd; m; mp; max; points
Just right: numTeamMembers, teamMembersCount



There are two rules that are common:

Rule 1: Prefer standard libraries over other libraries and self-written code.

It makes no sense to write a raw loop for summing the vector of numbers:

 int max = v.size(); // : ,    double sum = 0.0; for (int i = 0; i < max; ++i) sum = sum + v[i]; 

Just use std::accumulate from STL.

 auto sum = std::accumulate(begin(a), end(a), 0.0); //  

This rule reminded me of the words of Sean Parent (Sean Parent) from CppCon 2013: “If you want to improve the quality of the code in the organization, replace all the coding principles with one goal: no raw cycles!”.

Literally: if you write a raw loop, most likely you just do not know the STL algorithms.

Rule 2: Prefer suitable abstractions before using language features directly.

The next deja vu. At one of the recent C ++ seminars, I discussed for a long time and even longer carried out a detailed analysis of several intricate self-made functions for reading and writing strstreames. The participants were supposed to maintain these functions, but after a week they could not figure them out.

Understand the functional interfered with the wrong abstractions on which it was built.
For example, let's look at a self-made function for reading std::istream :

 char** read1(istream& is, int maxelem, int maxstring, int* nread) // :    { auto res = new char*[maxelem]; int elemcount = 0; while (is && elemcount < maxelem) { auto s = new char[maxstring]; is.read(s, maxstring); res[elemcount++] = s; } *nread = elemcount; return res; } 

And, in comparison, how much easier is the following function:

 vector<string> read2(istream& is) //  { vector<string> res; for (string s; is >> s;) res.push_back(s); return res; } 

Proper abstraction often means that you don’t have to think about owning, as in the read1 function. And it works in read2. The caller read1 owns the result and must delete it.

The ad enters the name into scope. But honestly, I am biased. On the one hand, these rules can be boring, because in many ways obvious. On the other hand, I have seen enough code breaking these rules. For example, I once spoke with a former Fortran programmer who believed that each variable should consist of exactly three characters.

In any case, I will continue to explain the rules, because good names help make the code easier to read and understand, support and extend.

Here are the first six rules.
(the numbering goes as in the article. The author missed paragraphs 3 and 4 since they do not correspond to the subject)

Rule 5: Keep a small area of ​​visibility.

The code will take no more than a screen and one look is enough to understand how it works. If the scope is too large, structure the code and divide it into functions and objects with methods. Identify logical entities and use obvious names in the refactoring process. This will make your code much easier.

Rule 6: Declare names in initializers and conditions of the for-operator to limit the scope

We could declare a variable in the for statement since the time of the first C ++ standard. And in C ++ 17, we can declare variables in both if and switch .

 std::map<int,std::string> myMap; if (auto result = myMap.insert(value); result.second){ // (1) useResult(result.first); // ... } else{ // ... } //    // (2) 

The variable result (1) is valid only within the if and else branches of an if if . Therefore, the result will not litter the external scope and will be automatically destroyed (2). This feature is only in C ++ 17, before the result need to be declared in the outer scope (3).

 std::map<int,std::string> myMap; auto result = myMap.insert(value) // (3) if (result.second){ useResult(result.first); // ... } else{ // ... } 

Rule 7: Shared and local names should be shorter than rare and non-local

The rule may seem strange, but we have become accustomed. Assigning the names i , j and variables, we immediately make it clear that i and j are indices, and T is the type of the template parameter.

 template<typename T> // good void print(ostream& os, const vector<T>& v) { for (int i = 0; i < v.size(); ++i) os << v[i] << '\n'; } 

There is a meta rule for this rule. The name must have been obvious. If the context is small, you can quickly understand what the variable is doing. But in a long context, it is more difficult to understand, so you need to use longer names.

Rule 8: Avoid Similar Names.

Can you read this example without confusion?

 if (readable(i1 + l1 + ol + o1 + o0 + ol + o1 + I0 + l0)) surprise(); 

To be honest, I often have difficulty distinguishing between the number 0 and the capital letter O. Because of the font, they may look almost the same. Two years ago, it took me a long time to login to the server. Just because the automatically generated password contained the character O.

Rule 9: Do not use names written FULLY_CAPSOM

If you write the names of FULLY_CAPSOM, then be ready to face the replacement with macros - it is in them that it is often used. In the part of the program presented below, there is a small surprise:

 // -  : #define NE != // -   : enum Coord { N, NE, NW, S, SE, SW, E, W }; // -   .cpp  : switch (direction) { case N: // ... case NE: // ... // ... } 

Rule 10: Declare (only) one name at a time

I will give two examples. Noticed both problems?

 char* p, p2; char a = 'a'; p = &a; p2 = a; // (1) int a = 7, b = 9, c, d = 10, e = 3; // (2) 

p2 is just char (1) and c not initialized (2).

In C ++ 17, there is one exception to this rule for us: structured binding.
Now I can make the if expression with the initializer from rule 6 even more readable.

 std::map<int,std::string> myMap; if (auto [iter, succeeded] = myMap.insert(value); succedded){ // (1) useResult(iter); // ... } else{ // ... } // iter  succeeded   // (2) 

THE END (to be continued)

If you have questions, comments, then we are waiting for them here or at our Open Day .

Source: https://habr.com/ru/post/349586/


All Articles