📜 ⬆️ ⬇️

Named C ++ Parameters. Not useful

From time to time, it suddenly starts to want named parameters in C ++. Not so long ago there was an article , and some time ago he himself wrote on this topic. And what is surprising is that since the time of that article I have been participating in a new project without having to drag the old code behind me, and somehow I don’t use all this described by myself. Those. figured out the question, admired the prospects ... and continued to work the old-fashioned way! How so? Laziness? Inertia? I will try to give the answer under a cat.

To begin, consider an example - the declaration of a function that returns a date object for a given day, month, and year.

Date createDate(int day, int year, int month); 

The problem is obvious - what order of the parameters you do not choose, in a month, after seeing such

 Date theDate = createDate(2, 3, 4); 

will you wonder: “What is this? March 2, 2004, or the fourth of 2002? If it is particularly lucky, and the team is international, the interpretation of the function by the developers may be completely different. Identical types go in a row in the parameter list ... In such cases, you usually want named parameters, which in C ++, alas, no.
')
Many programmers have to switch from one programming language to another. At the same time, something in the new language is like, something is not ... The bad is forgotten over time, but you want to bring good things to the environment where you are working now. There, in the same Objective-C, there are named parameters!

 + (UIColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha 

Yes, the first parameter goes without a name, but its name is often included in the name of the method. The solution, of course, is not ideal (for example, the colorWithBlue method does not exist, such is the color injustice), but the named parameters are supported at the language level. VBA is still better - all parameters can be given names, thanks to them it is possible not to produce many similar methods, but to do with one. For example, look at Document.PrintOut

And in C ++ there is nothing like that! Just want to correct the situation, look for libraries, invent crutches and bicycles. And even something will be and will turn out. But instead, you can think, if everything is so beautiful, why the named parameters are not added. So many paradigms are supported, and here it is ...

Or maybe added? Just called differently. For example, custom types. It's time to bring the main idea of ​​the article. Primitive standard types have no place in the interfaces of the real system . These types are just building blocks from which you need to build, rather than try to live inside.

For example, an object of type int is a signed integer lying in a certain range. What does this type describe? Only its implementation. It can not be, for example, the number of apples. Because apples can not be minus 10. Still worse: unsigned int is also unsuitable for this task. Because apples have nothing to do with the type of data on your platform. By binding the primitive types of the language to the parameters of the open methods of our models, we make a mistake, which we then try to “hush up” with various crutches. And in our desire to hide the mistake, we often ignore the simple fact that the language is trying to help us, saying: "I do not support this directly, and not without purpose ...".

But the main disadvantage of primitive types is that the compiler loses the chance to detect a logical error. For example, we have a method that takes two parameters - a name and a surname. If we reduce them to standard string types, the compiler will see only two pieces of text, from the permutation of which the meaning will not change. As a result, one developer will give the first parameter the first name, and the other the last name. And both will be technically right. The error is destroyed in the germ if there are special types for the first and last names. In a real system, where the first and last names are so important entities that enter the interface separately, simply reducing them to strings is an error. A name is not an arbitrary string. If only because it is selected from a previously known set. It also, say, does not contain numbers (although here I am not sure).

But back to the dates. Day is by no means unsigned int , not unsigned char, not even std :: string . Day is ... day! If we build a model in which there are dates, then it makes sense to create a special type to represent the days. User-defined data types are exactly what gives C ++ all its power and expressiveness. Then the crutches are not needed. Enter class to represent days

 class Day { explicit Day (unsigned char day); //... private: unsigned char mValue; }; 

Something like that. Naturally, for the physical representation of a value in memory, we still use the primitive type. But this is no longer part of the interface. Immediately we get full control over the contents of this field, eliminating a number of possible errors. Yes, without knowing the full date, exact limitations cannot be established, but at least a check for getting into the interval of 1..31 can already be organized. The most important thing is that by implementing special data types for the month and year with explicit constructors for initialization with primitive types, we get the named parameters supported by the language itself. The function can now be called as follows.

 Date theDate = createDate(Day(2), Month(3), Year(4)); 

No detours, no additional libraries. Yes, changing the parameters of this approach will not allow, but it is not so important. The main mission of the named parameters is to eliminate errors when calling functions and improve the readability of the code - is carried out.

As a bonus, we obtain increased flexibility if, say, we wanted to set the month also in string form. Now it is possible not to make the overloadable variant createDate (this is a function of creating a date object, which in general it is up to the month format). Instead, another explicit month constructor is simply added.

 class Month { explicit Month(unsigned char month); explicit Month(std::string month); //... private: unsigned char mValue; }; 

Everyone now does his job - createDate creates a date, and the Month class interprets and controls the correctness of the month value.

 Date theDate = createDate(Day(2), Month("Jan"), Year(4)); 

Immediately I would like to argue - isn’t there going to be too many extra types if you make your own type-wrapper for every primitive type? Then how to look. If you are a student who needs to quickly write a lab, give it up and forget, then yes - a lot of extra code and lost time. But if we are talking about a real system, in which you are interested in a long and happy life, then I wouldn't call the user types for the entities used in the interface superfluous.

But what about custom types? What, for example, to do if a method accepts several objects of the same type

 User user1, user2; //... someMethod(user1, user2); 

It all depends on the context. If all objects are equivalent, then there is no problem - nothing changes from the order of their transmission. To emphasize this, you can only transfer objects packed into an array or another container. If the objects are unequal, for example, the method subordinates user2 to the object user1, then special types reflecting the role of objects will be quite useful. It should be wrappers around user objects (as is the case with primitive types) or it is easier to create special classes that inherit from User depends on the system being implemented. It is important to somehow express the different roles of user1 and user2 by means of the language, allowing the compiler to catch errors related to their possible confusion.

What can be concluded. No need to strive to grab all the best of all languages ​​and shove it into one, and without that long-suffering C ++. It is important to be able to overcome inertia when changing a programming language. Say yes, in Lua you can assign values ​​to several variables at once.

x, y = getPosition ()

The idea itself is beautiful, but is it necessary in C ++. Generally not needed. It is easier to create a type of Position and assign a value to its object. Language is a tool, nothing more. And from the fact that the tools are sometimes similar, it does not at all follow that you need to use them equally down to the smallest detail.

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


All Articles