Reflection on C++ [transfer]

In the past two years, many people have come out to follow C++, and the followers are full of various senses of superiority. It seems that if you don't write C++, you will be a low-end programmer all your life. Faced with this phenomenon, should you come out and hack C++ in time? ? Hehehe.

Let's have some fun, there are countless jokes about C++:

Joke: C++ is an unlucky language. It is said that Boeing used ADA to program aircraft hardware, and it has been used well. Later, they recruited a group of college students. The students said that I was still using such a backward language, and then changed The plane crashed after refactoring into C++.

Joke: What is a C++ programmer? It is a program that was originally written in 10 lines. He had to use 30 lines to complete it, and called himself "encapsulation", but every time he came to the second project, he broke 80% and rewritten it, and called it "refactoring". ".

Joke: C is easy to miss and hit your own feet. Although it is not easy to use C++, once you miss it, your whole leg will be blown away.

Joke: Programmers who studied Java for two years at the same time discussed object-oriented and design patterns, while programmers who studied C++ for two years at the same time discussed what happened to templates and various language specifications.

Joke: People who teach others to learn C++ make a lot of money, and many people who really use C++ die miserably.

Joke: There are too many places in C++ that allow a person to express themselves as "smart", so the longer one uses C++, the more likely they will feel that they are "smart" and end up in a trap without knowing it. Learn C++ well.

Joke: Many people who have written C++ programs for more than ten years still do not know how many specifications C++ has, and still fall into certain pits from time to time.

Joke: Many people who think that C++ is convenient for cross-platform, when actually writing cross-platform code, will find it difficult to find two C++ compilers that support the same standard. 
—————

Q: Then why does C++ still see so many fans? 
A: Actually, it is because of Windows, because the rise of Windows has led to C++. C++ was originally a language only suitable for developing GUI.

Q: Why is C++ only suitable for developing GUI? 
A: You see that there is no GUI under Unix, why is there only C? All system-level problems can find mature solutions in C, and application-level problems can be solved well in other high-level languages. Where is C++?

Q: You are arrogant. There are also C++ projects under Unix. 
A: Yes, yes, you can still write any bad code in any language.

Q: Stop talking nonsense, what are you talking about? Even C++ and Windows are tied together. 
A: Think back to the time when a big bull was teaching a group of beginners how to program. While developing, point to the screen and say, you see, this is a Button, we can use an object to describe it, that is a panel, we can also use an object to describe it, and have you noticed, in fact, Button and Panel They are related by blood, you see. . . This is how it came out. . . . The following students used to study backward school textbooks, and some even used turboc's bgi library to draw some dots and circles. Where have I seen such a gorgeous Windows interface. Daniel's words are engraved on his young mind like a golden rule. While learning Windows, I found that, sure enough, they all need a base class. Sure enough, they are brothers, and they share some basic attributes, which can be placed in the base class. The more they use it, the better they feel. They subconsciously feel that because C++ can help them solve so many interface problems so smoothly, it seems that C++ can help them solve all problems. So after developing the interface, they continued to develop it. When they encountered various design problems, they believed that they must not use C++ well. So I forced myself to use it, and then it was over.

(click more to expand)

 

————— 
I have a lot of jokes about C++, you C++ fans don’t need to be seated. Closer to home, why hack C++? Not to mention black or not, I have been using C++ (previously C and Pascal) since 1994, and I have watched C++ grow and grow along the way. The code written in C++ should add up to more than 10MB. I have also read it, and it was not until 2004 that I started to switch back to C. The main reason was that I found many problems that could not be solved with C++ thinking, or problems that would be very bad to solve with C++ thinking.

At that time (2004-2005), it was when C++ was flying all over the sky. It was necessary to call C++, and the template must be used. I jumped out and said that you wake up, don’t go too far, the world is not all abstract data structures and algorithms can be described clearly. So many people jumped out excitedly and said: "You don't understand the essence of C++, you don't know how to use C++ at all". I asked them: "Language is used to solve problems. If a language often falls into the ditch after three or four years of learning, is it considered a good language? If it is difficult for programmers who have written C++ for more than ten years to master it, this is considered good. language?" They say again: "Language is dead, man is alive".

I remember that a domestic C++ expert at that time, in order to correct my "wrong point of view", showed me a set of very powerful libraries written by him. I opened it and took a breath. All are .h files. I can only reply to him with three words: "You are awesome". Of course, this is an extreme example. The guy finally started to gradually move the contents of .h into .cpp, which is a good thing.

He was in the same company with Yunfeng at that time. During the training for newcomers in 2004, he assigned a newcomer to implement a memory allocator. When correcting the homework, he often asked people while watching, "It's not enough C++, can you do 100% OOP? ?", "Don't keep 1% of C's". I sent a question about C++ on the company's internal mailing list at the time, and most people said: "How can we write a 3D engine without C++?". I told them: "John Carmack was still using ANSI C until Quake3, and then he switched to C++ because he had to support D3D. Why can't C write a 3D engine?". They told me: "Look, Point, is an object, Matrix is ​​also an object, so many Vector algebraic calculations, what a wonderful thing to do with C++ arithmetic overloading, the three-dimensional world is the world of objects.".

Indeed, at that time, the client GUI only had C++, and the graphics engine only had C++. These two are the strongest aspects of C++, so I did not argue with them, forcing them to admit that C can also write graphics beautifully, and C writes can be written very elegantly. It's not that I have nothing to do, so why should I question other people's core values, hehe. At that time, I was taking over a C++ project with more than 800KB of code. Every crash took a long time to locate. There were a lot of dependencies in the project. If you change one place, you need to look at several places before and after. If you miss one, the whole system is stupid. . After I started refactoring, I drew for two weeks, stripped out the performance-sensitive core parts and implemented them in C (the amount of code was only 200KB), then exported the Python interface, and used Python to complete the rest. The code amount of the entire script layer is only 150KB. The whole world is refreshed. The original construction period of the entire C++ project was 2 programmers and 4 months. The time I spent refactoring alone was 1.5 months, and the amount of code was more than twice as long as it was far away. All kinds of peculiarities The bugs are also swept away. I looked at the 800KB mess of C++ code on the left, and then looked at the tidy 300KB pure C + Python on the right, thinking, why didn't this project do this from the beginning?

 

cross-language interface

 

 

 

Modern project development requires not only higher performance, but also more powerful language description capabilities. And C++ is in an embarrassing place. Compared with the bottom layer, it is not as accurate as C in controlling memory and hardware. Various implicit structures make you hard to guard against. Compared with description ability, rapid business development and error location, it can't keep up with Python. Dynamic languages ​​such as Ruby and Lua are at the point where both the east and west are squeezed and eroded.

Soon, around 2006-2007, various problems of misusing C++ by other project groups began to appear: scripting had achieved great success in engineering practice at that time, but some projects had to pursue 100% C++ on the one hand, On the other hand, they need to export the interface to the script. They found a problem and don't know how to export a large number of C++ basic libraries and interfaces to Lua.

There are various convenient ways to export the C interface to the script. However, the entire project was developed by a group of cpp gurus who have never used scripts. When they wanted to export the cpp class to the script, they designed a set of awesome system, lua automatically generates machine code to call various classes of c++, yes, it is the cffi or ctypes of the c++ version. He wrote a set of machine code generation for classes that call vc, and a set of code generation for classes that call gcc. After the cpp Daniel finished writing, he showed off his achievements everywhere. Later, he resigned, and the project went online again and again with unverifiable problems. Later, Yunfeng went to support the project team. This set of intertwined C++ projects , this large-scale code self-generation system deeply disgusted him. Later, as we all know, Yunfeng began to oppose C++ and advocated returning to C. I don’t know if it has anything to do with this project.

So I found an interesting phenomenon. Anyone who is good at using scripts to improve engineering efficiency basically uses C plus dynamic language to solve most problems (except gui and graphics). Many people who think that C++ rules the universe have never used it. Scripts or people who don't know how to use them.

With this method, when our products compete with competitors, they have the same function and the same manpower configuration. Competitors use pure C++ to develop in three months, but we can get them out in a month. At the same time, competitors can only try and make mistakes. Once, we can trial and error three times. Later, according to our recruited colleagues, competitors also began to gradually reduce the proportion of C++ and increase the proportion of Java, which is a good thing, everyone is making progress.

 

ABI's embarrassment

 

 

 

The C++ interface at the ABI level has never been standardized. Using classes as interfaces will introduce many hidden problems, such as memory problems. A class is instantiated in a library. If it is released in another library, there will be many problems, because two Dynamic libraries may have different memory management systems. You use the allocator here to allocate a piece of memory, and then use the allocator there to release it, no problem. Many solutions are to add a Release method (such as DX) to tell people outside that they should call Release instead of delete when they are used up.

It is normal for the project to write large modules and isolate them into dynamic libraries, and it is also normal for various third-party libraries and self-written libraries to introduce specific memory management mechanisms for the pursuit of high performance. Many people don't pay attention to the place where release should be called and write it as delete and fall into the ditch. More winners have defined many classes with inline methods across ABI. As a result, various implicit constructions and destructors are actually generated in this library, and that library is destructed, making it a mess. C is much clearer. You call fopen when constructing, and fclose when destructing. There is no ambiguity. In fact, the contradiction of C++ is that on the one hand, it admits that memory management as a system-level language should be left to the user's decision, but on the other hand, it defines many memory operation behaviors that are not controlled by the user . Therefore, the C++ standard across the ABI layer has not been defined, not because the polymorphic abi is complicated, but because the language logic contradicts each other . In order to make up for this contradiction, C++ introduces operator new and delete. This new/delete overload is a patch that does not logically make the language complete. Its appearance further drags users into the abyss of bugs.

In fact, when we look back at this problem today, we can find two basic principles: the introduction of an uncontrollable memory mechanism at the cross-abi level is problematic in terms of language, and we can only rely on developers to agree on various smart base classes and Agreed to develop specifications to solve this problem, which cannot be solved at the language layer; secondly, since you have defined various implicit constructions and destructors, you should completely take over the memory like java or dynamic languages, and do not allow users to customize any memory. Management methods, not on the one hand, as a system pole language, to give users the freedom to control, on the other hand, they have to rush to control together with users.

Therefore, the object layer ABI interface cannot be standardized for a long time. The pure C ABI can not only easily cross dynamic libraries, but also easily integrate with assembly and various languages. When this topic was discussed back then, C++ gurus began to repeat those golden rules to refute me: "Language is just a move, if you practice your internal skills well, you will be able to win with no moves, and even if you pick up grass, you can be a swordsman. , although there are many pits in C++, you can do it well if you don’t use it so well.” I said: things that should have been solved at the language level, due to the incomplete language logic, throwing a large number of problems to developers to solve greatly increases the thinking burden of developers, just like a broken house. You have seen Jin Yong too much, no matter how high the martial arts is, when you get a gun and find that the bullets do not necessarily shoot forward, but occasionally shoot backwards, should you concentrate on hitting the enemy? Or are you always on guard against your own bullet being shot at you?

 

 

 

System-level frustration

 

 

 

C++'s thwarted forays into things like embedded and operating systems are close to the hardware layer. Everyone feels that the programming language at the cosmic level is naturally capable of all tasks, and soon found several problems:

  • Unable to allocate memory: It turns out that C can be completely independent of memory allocation, and it is possible to write thousands of lines of code without a malloc. After the embedded processor is powered on, it jumps to a specific address (such as the start address 0). The first instruction is generally written in assembly and fixed at address 0, which is to simply initialize the stack and then jump to the start of the C language. Function to go, imagine that the memory allocation mechanism has not been established at this time, you have defined two classes, how to construct it? Most microprocessors with limited resources use a piece of static memory to operate. C++ is cool to write, but when all kinds of implicit constructs appear, it's stupid.
  • Standard library dependency: At the language level, all the features of the C language can run without relying on any library , which brings very convenient features for writing system layer and cross-platform and cross-language code. And C++ is not good, I want to construct, I want exceptions, why can't you give me a powerful runtime? What do you still want to use stl? Don't look at how bloated the library is (memory footprint, code size).
  • Exception handling problem: The underlying development needs to strictly handle all error returns, call this line, and judge the error in the next line. Exception is a loose error handling method. It is fine for the application layer to write this way, but it is very embarrassing for the system layer to write this way. What is the difference between calling a try on each line and the result of the if judgment after C's call? The constructor of C++ has no return value. If there is an error in the construction, you must be forced to catch the exception of the constructor. Even if you catch the exception, of course, the destructor of the relevant internal object will be automatically triggered when the exception is constructed, but there are many parallels. For resources without destructors (such as system resources, such as C interface resources, they do not have a destructor), the whole process is difficult to control. At this time, this instance is a semi-initialized instance, how should you deal with it? So someone removed the initialization code from the constructor, only initialized the variables during construction, and added an init function with a return. This kind of code is much more redundant than C. What's more, when a hardware interrupt occurs, your colleagues transfer to some third-party libraries without your knowledge, and you don't catch the new exception at the outermost layer. Where should this exception be thrown? When the memory is not enough, you want to throw an OutOfMemoryException, but the memory is not enough, what should you do if you are completely unable to construct this exception?
  • Processor compatibility: C++ class depends on the addressing mode of base address + offset address. Many non-Intel series microprocessors only have simple given address addressing, which does not support such a statement to achieve BASE+OFFSET addressing. Many C++ codes are compiled and require more instructions to calculate addresses, resulting in a lot of performance degradation, which is not worth the loss.
  • Implicit operation problem: C is characterized by simplicity and directness. You can clearly know what each line of statement will be translated into, and the system will execute it strictly according to your code. In C++, such as str1 = str2 + "Hello" + str3;, few people really know how many times of construction and copying. It is very irresponsible to write the underlying code in this way, and the underlying needs More precise and strict control, stronger control in C language .

Of course, many people here say, "C++ is a superset of C, you can write it in C in certain places. No one forces you to construct classes or use exceptions." That's right, according to Linus Said: " If you want to use C++ to write excellent portable and efficient code at the system level, you will eventually be limited to using the functions provided by C itself, and these functions have been perfectly provided by C, so the significance of using C at the system layer is It is to exclude the interference of other features of C++ at the language layer ."

Many people remember Linus bombarding C++ once in 2007 because someone asked Git why it didn't use C++ development. In fact, when C++ was at its peak in 2004, someone asked why the Linux kernel was not developed in C++, and he bombarded it once:

In fact, we tried C++ on Linux in 1992. Disgusting, believe me, writing a kernel in C++ is a "BLOODY STUPID IDEA". In fact, C++ compilers can't be trusted, they were worse in 1992, and some basic facts have never changed:

- The entire C++ exception handling system is "fundamentally broken". Especially for writing kernels. 
- Any language or compiler that likes to hide behavior (like memory allocation) behind your back is not a good choice for developing a kernel. 
– It’s still possible to write object-oriented code (such as filesystems) in C without having to write shit in C++.

In general, for anyone who wants to use C++ to develop a kernel, they are introducing more problems and can't see exactly what they are writing as clearly as C.

C++ fans tried to introduce C++ into system-level development at the height of C++'s popularity, but never succeeded. Therefore, whether it is an embedded or an operating system, in the development close to the bottom layer of the hardware, it is all C code, and there is no place for C++ at all.

 

 

 

Application layer reflection

 

 

 

After STL came out, it gave people the illusion that C++ could facilitate the development of application layer logic. Due to many things that are not rigorous at the language layer, STL is done in the form of patches, so many beginners who think that they can write C++ like Java have fallen into pits one by one. For example, list.size(), under Windows, the stl of vc saves the length of the list, and size() returns the variable directly in O(1), while in the stl of gcc, the length of the list is not saved, and size() will search For all nodes, O(n) speed returns.

Since the language layer does not support strings, the implementation of std::string is very inconsistent. You copy and construct a string, and some implementations use the copy-on-write method to reference. Some places are new, some are implemented using memory pools, some are thread-safe, and some are thread-unsafe. You can't tell what exactly is done after the same statement (see Meng Yan's " The father of Linux is not rough ").

Let's say I want to use hash_map, for cross-platform (when you actually write cross-platform code, it's hard for you to decide the target compiler and their version, and you can't use unordered_map if you want), it's hard for me to point out a unique way to declare hash_map method, in order to ensure the normal use of hash_map under different compilers, you have to write this:

#ifdef __GNUC__ 
    #ifdef __DEPRECATED 
        #undef __DEPRECATED 
    #endif 
    #include <ext/hash_map> 
    namespace stdext { using namespace __gnu_cxx; } 
    namespace __gnu_cxx { 
        template<> struct hash< std::string > { 
            size_t operator()( const std::string& x ) const { 
                return hash< const char* >()( x.c_str() ); 
            } 
        }; 
    } 
#else 
    #ifndef _MSC_VER 
        #include <hash_map> 
    #elif (_MSC_VER < 1300) 
        #include <map> 
        #define IHAVE_NOT_HASH_MAP 
    #else 
        #include <hash_map> 
    #endif 
#endif

#ifdef __GNUC__ 
    using namespace __gnu_cxx; 
    typedef hash_map<uint32_t, XXXX*> HashXXXX; 
#else 
    using namespace stdext; 
    typedef hash_map<uint32_t, XXXX*> HashXXXX; 
#endif

If there is a better cross-platform writing method, please tell me, I really can't stand it anymore. A basic container is so hard for people to use, so that many C++ programmers are thinking about various specifications all day long, and have no time to really think about programming.

Because the language layer needs to be compatible with C, and it is not willing to only do the work of the system layer like C, as a result, when C++ sets foot in the application layer, it cannot take over memory management, cannot support language layer strings, and cannot implement language layer basic containers. . So it is necessary to use something like stl to provide convenience, but stl itself is full of various pits. Not to mention the problems of large memory usage and large program size, when the compilation speed is enough. So why is everyone in C++ willing to repeat the wheel and implement various basic containers and strings, so that almost every different C++ project has its own specific string implementation. It is because everyone stepped on the pit that they began to feel that they needed to control these details by themselves. The starting point of stl is good, but it can only be used casually in simple small programs. It is really used for large projects, and stl is easy to lead people into the ditch, so many large C++ projects implement a set of STL-like things by themselves. , doesn't this violate the original intention of stl design?

The lack of the language layer allows everyone to create a lot of basic and smartly designed base classes to meet the needs of rapid iteration of business development to provide functions such as garbage collection, reference counting, copy-on-write, delegate, and so on. . Each project has a series of basic classes such as BaseObject, which introduces a misunderstanding. After two years, you look at your code and find that a BaseObject does not meet the requirements, or when you merge the code with another project, you need to merge it. some fundamental properties. Graphics and GUI models that remain unchanged for thousands of years are fine. Application classes are ever-changing. Once these smartly designed base classes no longer adapt to project development, they often face the cost of comprehensive adjustment.

Open the blogs of C++ big cows, many places teach you the principle of std::string, and things to pay attention to. The limitations of map, the principle of vector, teach you how to implement a string. This is called "mental burden," and it distracts you, something you never see in other languages. Soldiers don’t study how to go to the front line to kill the enemy. They are pondering the principle of grabbing and guns every day. They are thinking about how to use guns so that they won’t go off fire, and how to use guns so that they won’t blow themselves up. How can they fight this battle?

So in the following years, more and more people began to reflect on the problems caused by the overheating of C++ in the past two years, such as Martin Sustrik, author of the high-performance network library ZeroMQ: " Why I want to use C instead of C++ to implement ZeroMQ ", such as Yunfeng's " Yunfeng's Blog: The Return of C ", such as " Why C++ Is Not "Back" ", which caused heated discussions .

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324594050&siteId=291194637