⬆️ ⬇️

Five popular myths about C ++, part 2

Part 1



4.2 Shared ownership shared_ptr


Not every object may have one owner. We need to make sure that the object is destroyed and freed when the last link to it disappears. Thus, we need a model of shared ownership of the object. Suppose we have a synchronous queue, sync_queue, for communication between tasks. The sender and recipient receive a pointer to sync_queue:



void startup() { sync_queue* p = new sync_queue{200}; // ! thread t1 {task1,iqueue,p}; // task1   *iqueue    *p thread t2 {task2,p,oqueue}; // task2   *p    *oqueue t1.detach(); t2.detach(); } 




It is assumed that task1, task2, iqueue and oqueue have already been appropriately defined somewhere and I apologize for the thread going through the scope where they were created (using detatch ()). Question: who will remove the sync_queue created in startup ()? Answer: the last person to use sync_queue. This is a classic case when garbage collection is required. Initially, the assembly counted the pointers: you need to store the number of uses of the object, and at the moment when the counter is reset, delete it. Many modern languages ​​work like this, and C ++ 11 supports this idea through shared_ptr. The example turns into:

')

 void startup() { auto p = make_shared<sync_queue>(200); //  sync_queue    stared_ptr   thread t1 {task1,iqueue,p}; // task1   *iqueue    *p thread t2 {task2,p,oqueue}; // task2   *p    *oqueue t1.detach(); t2.detach(); } 




Now, task1 and task2 destructors can destroy them shared_ptr (and in most properly built systems they will), and the last thing to do is destroy sync_queue. It is simple and quite effective. No complicated system. Importantly, it does not just return the memory associated with sync_queue. It returns a synchronization object (mutex, lock, whatever) embedded in sync_queue to synchronize two threads performing two tasks. This is not just memory management, it is resource management. This “hidden” synchronization object is handled in the same way as file and stream handles in the previous example. You can try to get rid of the use of shared_ptr by entering a unique owner in any scope that encompasses the task, but this is not always easy to do - therefore C ++ 11 has unique_ptr (for single ownership) and shared_ptr (for shared ownership) .



4.3 Type Safety


I have talked so far about garbage collection in the context of resource management. But there is also type safety. We have a delete operation that can be applied incorrectly. Example:



 X* p = new X; X* q = p; delete p; // … q->do_something(); // ,  *p,    




Don't do that. The direct use of delete is dangerous and not necessary in ordinary cases. Leave the deletions to the classes that manage the resources — string, ostream, thread, unique_ptr, and shared_ptr. There the deletion is neatly tracked.



4.4 Outcome: Resource Management Ideals


From my point of view, garbage collection is the last resort for resource management, not a solution to a problem or an ideal.



1. Use suitable abstractions that serve their resources recursively and implicitly. Give preference to them, rather than variables in a certain scope.

2. When you need to use pointers and links, use smart pointers - unique_ptr and shared_ptr

3. If all else fails (for example, your code is part of a program that gets tangled in pointers and does not use a language-supported strategy for managing resources and handling errors), try processing non-memory resources manually and turn on assembly garbage to handle inevitable memory leaks.



5. Myth 4: for efficiency, it is necessary to write low-level code



Many believe that an effective code must be low-level. Some even believe that low-level code is necessarily effective. (“If it’s so ugly, surely it’s fast! Someone spent a lot of time and talent to create this thing!”). Of course, you can write effective code at a low level, and you need to make some code low-level to work with machine resources. Measure, however, whether it is worth your effort. Modern C ++ compilers are very efficient, and the architecture of modern machines is extremely complex. If necessary, such a low-level code should be hidden behind the interface for convenience. Often, hiding a low-level code behind a high-level interface contributes to optimization. Where efficiency is important, first try to reach it, having expressed the idea at a high level, do not immediately rush to bits and pointers.



5.1 qsort () in C


A simple example. If you need to sort a set of floating-point numbers in descending order, you could write code for this. But if you do not have extreme requirements (for example, there are more numbers than can fit in memory), that would be naive. Over the decades, we have made libraries with sorting algorithms with acceptable speed. I least like qsort () from the ISO standard C library:



 int greater(const void* p, const void* q) //   { double x = *(double*)p; //   double   p double y = *(double*)q; if (x>y) return 1; if (x<y) return -1; return 0; } void do_my_sort(double* p, unsigned int n) { qsort(p,n,sizeof(*p),greater); } int main() { double a[500000]; // … fill a … do_my_sort(a,sizeof(a)/sizeof(*a)); //      // … } 




If you are not programming in C, or if you haven’t recently used qsort, you need to explain something; qsort takes 4 arguments

- pointer to a sequence of bytes

- amount of elements

- item size

- a function that compares two elements that are passed as pointers to their first bytes



This interface hides information. We do not sort bytes - we sort double, but qsort does not know this, so we need to provide information on how to compare double, and how many bytes in double. Of course, the compiler knows such things. But the low-level qsort interface does not allow the compiler to use this information. The need to specify such simple information leads to errors. Am I confusing two whole qsort arguments? If confused, the computer will not notice. Is my compare () agreement in C for a three-way comparison? If you look at the industrial implementation of qsort (I recommend), you will see how much effort is made to compensate for the lack of information. For example, it is rather difficult to swap the elements specified as a number of bytes, so that it is as effective as swapping a double pair. Costly indirect calls to the comparison function can be eliminated by the compiler only if it uses constant distribution for function pointers.



5.2 sort () in C ++


Compare qsort with its equivalent of sort from C ++



 void do_my_sort(vector<double>& v) { sort(v,[](double x, double y) { return x>y; }); //  v   } int main() { vector<double> vd; // … fill vd … do_my_sort(v); // … } 




It requires less explanation. The vector knows its size, and we do not need to explicitly indicate the number of elements. The type of elements is not lost, and it is not necessary to remember their size. By default, sort sorts in ascending order, so I had to set a comparison criterion, just like for qsort. Here it is passed as a lambda expression comparing two doubles with>. And it so happens that this lambda is trivially inline by all C ++ compilers, which I know, so the comparison turns into a single machine operation “more than” - no ineffective function calls.



I used the container version of sort to not explicitly set iterators, that is, not to write:



 std::sort(v.begin(),v.end(),[](double x, double y) { return x>y; }); 




You can go ahead and use the C ++ 14 comparison object:



 sort(v,greater<>()); //  v   




Which version is faster? You can compile qsort versions of both C and C ++ without any differences in speed, so this will be more like a comparison of programming styles, rather than languages. Library implementations use the same algorithm for sort and qsort, so this is a comparison of programming styles, not algorithms. Of course, different libraries and compilers will have different results, but for each implementation a reasonable response to different levels of abstraction will be visible.



I recently ran out the examples, and saw that sort is 2.5 times faster than qsort. This may vary from compiler to compiler and from computer to computer, but never qsort has won a sort with me. Sometimes sort was performed 10 times faster. Why? In the standard C ++ library, sort is clearly higher than qsort, while being more flexible and general. It is type safe and parameterized on the storage type, item type and sorting criteria. No pointers, sizes, bytes. The STL library to which sort belongs belongs tries not to throw out any information. This leads to excellent inlining and good optimization.



Generalization and high-level code can outperform low-level code. Not always, but sort / qsort comparison is not a single example. Always start with a high-level, accurate, and type-safe version of the solution. Optimize as needed.



6. Myth 5: C ++ is designed for large and complex programs.



C ++ is a voluminous language. The size of the definitions is similar to C # and Java. But this does not mean that you need to know every detail in order to use it, or use all the functions directly in each program. Here is an example of using basic components from the standard library:



 set<string> get_addresses(istream& is) { set<string> addr; regex pat { R"((\w+([.-]\w+)*)@(\w+([.-]\w+)*))"}; //  -  smatch m; for (string s; getline(is,s); ) //   if (regex_search(s, m, pat)) //   addr.insert(m[0]); //     return addr; } 




I assume that you are familiar with regulars. If not - it's time to get acquainted. Notice that I rely on move semantics to simply and effectively return a potentially large set of strings. All standard library containers provide displacement constructors, so there is no need to mess around with new.



For the example to work, you need to include components:



 #include<string> #include<set> #include<iostream> #include<sstream> #include<regex> using namespace std; 




Check:



 istringstream test { //   ,   "asasasa\n" "bs@foo.com\n" "ms@foo.bar.com$aaa\n" "ms@foo.bar.com aaa\n" "asdf bs.ms@x\n" "$$bs.ms@x$$goo\n" "cft foo-bar.ff@ss-tt.vv@yy asas" "qwert\n" }; int main() { auto addr = get_addresses(test); // get the email addresses for (auto& s : addr) // write out the addresses cout << s << '\n'; } 




Just an example. It is easy to change get_addresses () so that it takes a regular list as an argument, so that it can search for a URL or whatever. It is easy to change get_addresses () so that it recognizes more than one occurrence of a pattern in a string. C ++ is intended for flexibility and generalization, but not every program must be a framework. The bottom line is that the task of retrieving emails from a stream is simply expressed and simply checked.



6.1 Libraries


In any language, writing a program only through the built-in language features (if, for, and +) is tiring. Conversely, if there are suitable libraries (graphics, route planning, database), any task can be accomplished with reasonable effort. The standard ISO C ++ library is relatively small (compared to commercial ones), but besides it there are a lot of libraries with both source code and commercial ones. For example, using the Boost, POCO, AMP, TBB, Cinder, vxWidgets, CGAL libraries, complex things become easier. For example, let our program retrieve a URL from a web page. To begin with, we will generalize get_addresses () to find any string that matches the pattern.



 set<string> get_strings(istream& is, regex pat) { set<string> res; smatch m; for (string s; getline(is,s); ) //   if (regex_search(s, m, pat)) res.insert(m[0]); //     return res; } 




This is a simplified version. Now you need to somehow read the file from the web. Boost has an asio library for working with the web:



 #include <boost/asio.hpp> //  boost.asio 




Communication with the web server is quite difficult:



 int main() try { string server = "www.stroustrup.com"; boost::asio::ip::tcp::iostream s {server,"http"}; //   connect_to_file(s,server,"C++.html"); //     regex pat {R"((http://)?www([./#\+-]\w*)+)"}; // URL for (auto x : get_strings(s,pat)) //   cout << x << '\n'; } catch (std::exception& e) { std::cout << "Exception: " << e.what() << "\n"; return 1; } 




When parsing the file www.stroustrup.com/C++.html it gives:



www-h.eng.cam.ac.uk/help/tpl/languages/C++.html

www.accu.org

www.artima.co/cppsource

www.boost.org

...



I used the set, so the URLs are listed alphabetically.

I hid the connection check in connect_to_file ():



 void connect_to_file(iostream& s, const string& server, const string& file) //         s //   { if (!s) throw runtime_error{" \n"}; //      s << "GET " << "http://"+server+"/"+file << " HTTP/1.0\r\n"; s << "Host: " << server << "\r\n"; s << "Accept: */*\r\n"; s << "Connection: close\r\n\r\n"; //  : string http_version; unsigned int status_code; s >> http_version >> status_code; string status_message; getline(s,status_message); if (!s || http_version.substr(0, 5) != "HTTP/") throw runtime_error{ "  \n" }; if (status_code!=200) throw runtime_error{ "    " }; //   ,    : string header; while (getline(s,header) && header!="\r"); } 




I did not write everything from scratch. Working with HTTP is copied from the asio documentation.



6.2 Hello, World!


C ++ is a compiled language designed to create good, maintainable code for which speed and reliability matter. It was not intended for competitions with interpreted scripting languages ​​that are suitable for writing small programs. JavaScript and other similar languages ​​are often written in C ++. However, there are many useful C ++ programs that take up only a few dozen or hundreds of lines.



Authors of libraries can help here. Instead of concentrating on abstruse and advanced things in libraries, provide simple examples of “hello, world!”. Make a minimal version of the library, which is easy to install, and a one-page example of what it can do. At one time or another, we all find ourselves in the role of a novice. By the way, here is my version of “hello world” for C ++:



 #include<iostream> int main() { std::cout << "Hello, World\n"; } 




Longer and more complex versions seem less cool to me.



7 Applications of myths



Often myths have a basis. Each of them has moments and situations where they can be believed on a reasonable basis, based on evidence. To date, I consider them absolutely false, simple misunderstandings, although they were obtained in an honest way. The problem is that myths always serve a purpose, or they would have already become extinct. These five myths serve different purposes:

- they give comfort. No need to change, re-evaluate and rethink. The familiar seems pleasant. Changes are alarming, so it's good if the new product is unviable.

- you can save time. If it seems to you that you know what C ++ is, you don’t have to spend time learning something new, experimenting with new technologies, measuring code for speed, training beginners.

- you can not learn C ++. If these myths were true, why would he need to be taught at all?

- they help to promote other languages ​​and technologies - in the case of their truthfulness it would be necessary.



But they are false, so the arguments for keeping everything as it is, looking for alternatives to C ++ or avoiding the modern style of programming on it should not be based on these myths. Existing with an outdated concept of C ++ in the head may be comfortable, but when working with software it is necessary to change. More can be achieved than simply using C, C with classes, C ++ 98, etc.



The adherents of the "good old" lose. Support costs are often more than writing modern code. Older compilers and tools provide less speed and perform worse analysis than modern ones. Good programmers often refuse to work with antique code.



Modern versions of C ++ and the technology of programming that it supports differ for the better from the idea that “generally accepted myths” create. If you believe in any of them - do not take my word for it. Try it, check it out. Measure the "old way" and alternatives for the actual problem. Try to master new methods, explore new opportunities and technologies. Do not forget to compare the estimated cost of supporting the new and old ways. The best way to disprove a myth is to provide evidence. I presented my examples and arguments to you.



And I do not declare that C ++ is perfect. It is not perfect, it is not the best language for all and for all. Like any other language. Think of him as he is now, not as he was 20 years ago, and not as he is exposed by someone who advertises alternatives. To make a rational choice, look for reliable information, and try to understand for yourself how modern C ++ handles your tasks.



8 total



Do not believe the "generally accepted" knowledge of C ++, or its unsubstantiated use. This article discusses five popular opinions about C ++ and suggests that they are just myths:



1. To understand C ++, you first need to learn C

2. C ++ is an object-oriented programming language.

3. Reliable programs require garbage collection.

4. To achieve efficiency, it is necessary to write low-level code.

5. C ++ is only suitable for large and complex programs.



These myths are harmful.



9 Feedback



Any doubts? Let me know why. What other myths have you met? Why are they myths and not true? What evidence do you have of exposing them?



10 References



1. ISO / IEC 14882: 2011 Programming Language C ++

2. POCO libraries: pocoproject.org

3. Boost libraries: www.boost.org

4. AMP: C ++ Accelerated Massive Parallelism. msdn.microsoft.com/en-us/library/hh265137.aspx

5. TBB: Intel Threading Building Blocks. www.threadingbuildingblocks.org

6. Cinder: A library for professional-quality creative coding. libcinder.org

7. vxWidgets: A Cross-Platform GUI Library. www.wxwidgets.org

8. Cgal - Computational Geometry Algorithms Library. www.cgal.org

9. Christopher Kohlhoff: Boost.Asio documentation. www.boost.org/doc/libs/1_55_0/doc/html/boost_asio.html

10. B. Stroustrup: Software Development for Infrastructure. Computer, vol. 45, no. 1, pp. 47-58, Jan. 2012, doi: 10.1109 / MC.2011.353.

11. Bjarne Stroustrup: The C ++ Programming Language (4th Edition). Addison-Wesley. ISBN 978-0321563842. May 2013.

12. Bjarne Stroustrup: A Tour of C ++. Addison Wesley. ISBN 978-0321958310. September 2013.

13. B. Stroustrup: Programming: Principles and Practice using C ++ (2nd edition). Addison-Wesley. ISBN 978-0321992789. May 2014.



Afterword



After the publication of the article on isocpp.org received different comments. Let me comment on some of them.



Comments confirmed that this material is necessary. People repeat the old arguments. Unfortunately, many programmers do not read long articles, and short ones are discarded for incompleteness. The reluctance to read long articles prompted me to write this material and break it into three parts during initial publication.



This is not a research material that describes in detail every detail. As I wrote at the beginning: “A book can be dedicated to every myth, but I will confine myself to a simple statement and a brief statement of my arguments against them.”



However, many confuse examples to illustrate the point of view with the point of view itself. « », , , . , .. . , «» .



++11/++14, , . ++14 – ++ 1980-. , . , . , , . . - ++ – , ( ). .



, , . ++ 20- 10- . ++ , . , ++11. , ++11.



« » « Y X ». , , , ++, , , – . .



. - . , – . . , . , asio, 6.1 – , . ( ), . . , .6.2 – ++ , , . , 99% sort(v) sort(v.begin(),v.end()).



Speed ​​performance



My comments caused a small storm. Many tried to refute them by simple objections. I do not accept speed arguments that are not backed up by test data. My comments have been confirmed by real measurements in different situations over several years. Many of them are described in books. They are true for a wide range of similar examples.



++, . , , ++ ++11. , std::sort() std::string . – . GCC Clang –O2; Microsoft release mode.



. , , : , , , for, , . .



Yes, the C-version of compose () does not check the value returned by malloc (). I asked you if I did everything right. I deliberately did not give you a production code. The lack of verification of the result is one of the main sources of errors, so my “mistake” was specifically made to illustrate this. In this case, exceptions often help. Of course, you could write the C-version of compose () using the less well-known functions of the standard library, and yes, free storage could be avoided by letting the caller pass a buffer allocated on the stack and let the caller deal with the problem of string arguments that would overwhelm . However, these alternatives do not relate to the main issue: such code is more difficult to write than in C ++, and even harder to write it correctly. Newbies from the first time they write a version for C ++,but not for C, especially for those versions that are based on functions from the standard library that are not taught to beginners.



++ – ( ), F-35, F-16 ( ), : www.stroustrup.com/applications.html . , ++.



Libraries



, , . This is problem.But these libraries exist, and their research is often more productive than a simple movement forward, resulting in the invention of the next wheel.



Unfortunately, C ++ libraries are often not designed to work with others. And there is no one place where you could take all the libraries. For years I have been watching the process of teaching students according to the “first C” scheme, and have been reading these programs for decades. To thousands of people I taught C ++ as a first language. My statements about the possibility of learning C ++ are based on a lot of experience.



C ++ is easier to teach than C because of a better type system and syntax. Need to learn fewer tricks and crutches. Imagine how you would learn to teach C programming style by teaching the C ++ language. I would never give beginners a C ++ course that would:



- did not contain a good basis for working with memory, pointers, etc.

- did not give students an idea of ​​"pure C" and its use

- did not justify most of the possibilities of the language

- would try to teach absolutely all the techniques of C ++



Good teachers who teach C do not try to teach beginners to all the techniques.

www.stroustrup.com/programming.html - my answer to the question "How would you train new C ++?". This system works.



You can read my rather old work on some aspects of teaching C and C ++: Learning Standard C ++ as a New Language. C / C ++ Users Journal. pp 43-54. May 1999 (www.stroustrup.com/papers.html).



- , ++ — . ( ++).



++ — ISO ++14, , 30 , , 20 . C++11/C++14 , , . , ++. – ISO 11, K&R C ( , 11 , ++ ++14). , « ++».



C ++ is not an OOP language. This is a language that supports OOP, other programming techniques, and combinations thereof. If you are an experienced programmer, I recommend reading A Tour of C ++ as a quick overview of the modern C ++ language.

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



All Articles