Criticism of C++

{{Short description|Criticism of the C++ programming language}}

{{Use dmy dates|date=September 2021}}

{{lacking overview|date=September 2021}}

Although C++ is one of the most widespread programming languages,{{Cite web|title=Stack Overflow Developer Survey 2021|url=https://insights.stackoverflow.com/survey/2021/|access-date=2021-12-28|website=Stack Overflow|language=en}} many prominent software engineers criticize C++ (the language, and its compilers) arguing that it is overly complex{{Cite web|title=Google executive frustrated by Java, C++ complexity - Google, software, application development, Development tools, Languages and standards, Rob Pike|url=https://www2.cio.com.au/article/print/354210/google_executive_frustrated_by_java_c_complexity/|access-date=2021-12-28|website=CIO}} and fundamentally flawed.{{Cite web|title=C++ (Al Viro; Linus Torvalds; Theodore Ts'o)|url=https://yarchive.net/comp/linux/c++.html|access-date=2021-12-28|website=yarchive.net}} Among the critics have been: Robert Pike, Joshua Bloch, Linus Torvalds, Donald Knuth, Richard Stallman, and Ken Thompson. C++ has been widely adopted and implemented as a systems language through most of its existence. It has been used to build many pieces of important software such as operating systems, runtime systems, programming language interpreters, parsers, lexers, compilers, etc.

Complexity

One of the most often criticized points of C++ is its perceived complexity as a language, with the criticism that a large number of non-orthogonal features in practice necessitates restricting code to a subset of C++, thus eschewing the readability benefits of common style and idioms. As expressed by Joshua Bloch:{{Cite web |date=16 October 2009 |title=C++ in Coders at Work |url=https://gigamonkeys.wordpress.com/2009/10/16/coders-c-plus-plus/ |url-status=live |archive-url=https://web.archive.org/web/20171110061643/https://gigamonkeys.wordpress.com/2009/10/16/coders-c-plus-plus/ |archive-date=10 November 2017 |access-date=9 November 2017}}

I think C++ was pushed well beyond its complexity threshold, and yet there are a lot of people programming it. But what you do is you force people to subset it. So almost every shop that I know of that uses C++ says, "Yes, we're using C++ but we're not doing multiple-implementation inheritance and we're not using operator overloading." There are just a bunch of features that you're not going to use because the complexity of the resulting code is too high. And I don't think it's good when you have to start doing that. You lose this programmer portability where everyone can read everyone else's code, which I think is such a good thing.

Donald Knuth (1993, commenting on pre-standardized C++), who said of Edsger Dijkstra that "to think of programming in C++" "would make him physically ill":{{Cite interview |title=An Interview with Donald Knuth |url=http://home.hccnet.nl/h.vink/lead.htm |url-status=dead |archive-url=https://web.archive.org/web/20030430035506/http://home.hccnet.nl/h.vink/lead.htm |archive-date=30 April 2003 |website=Dr. Dobb's Journal |publication-date=April 1996 |interviewer=Jack Woehr}}{{Cite web |title=(La)TeX Navigator |url=http://tex.loria.fr/litte/knuth-interview |url-status=dead |archive-url=https://web.archive.org/web/20171120163257/http://tex.loria.fr/litte/knuth-interview |archive-date=20 November 2017 |access-date=10 November 2017}}

The problem that I have with them today is that... C++ is too complicated. At the moment, it's impossible for me to write portable code that I believe would work on lots of different systems, unless I avoid all exotic features. Whenever the C++ language designers had two competing ideas as to how they should solve some problem, they said "OK, we'll do them both". So the language is too baroque for my taste.

Ken Thompson, who was a colleague of Stroustrup at Bell Labs, gives his assessment:{{cite book |author=Peter Seibel |url=https://books.google.com/books?id=2kMIqdfyT8kC&pg=PA475 |title=Coders at Work: Reflections on the Craft of Programming |date=16 September 2009 |publisher=Apress |isbn=978-1-4302-1948-4 |pages=475–476 |access-date=9 November 2017}}

It certainly has its good points. But by and large I think it's a bad language. It does a lot of things half well and it's just a garbage heap of ideas that are mutually exclusive. Everybody I know, whether it's personal or corporate, selects a subset and these subsets are different. So it's not a good language to transport an algorithm—to say, "I wrote it; here, take it." It's way too big, way too complex. And it's obviously built by a committee.

Stroustrup campaigned for years and years and years, way beyond any sort of technical contributions he made to the language, to get it adopted and used. And he sort of ran all the standards committees with a whip and a chair. And he said "no" to no one. He put every feature in that language that ever existed. It wasn't cleanly designed—it was just the union of everything that came along. And I think it suffered drastically from that.

Slow compile times

The natural interface between source files in C and C++ are header files. Each time a header file is modified, all source files that include the header file should recompile their code. Header files are slow because they are textual and context-dependent as a consequence of the preprocessor.{{cite web|url=http://www.drdobbs.com/cpp/c-compilation-speed/228701711|title=C++ compilation speed|author=Walter Bright}} C only has limited amounts of information in header files, the most important being struct declarations and function prototypes. C++ stores its classes in header files and they not only expose their public variables and public functions (like C with its structs and function prototypes) but also their private functions. This forces unnecessary recompilation of all source files which include the header file each time these private functions are edited. This problem is magnified where the classes are written as templates, forcing all of their code into the slow header files, which is the case with much of the C++ standard library. Large C++ projects can therefore be relatively slow to compile.{{cite web|url=http://commandcenter.blogspot.de/2012/06/less-is-exponentially-more.html|title=Less is exponentially more|quote=Back around September 2007, I was doing some minor but central work on an enormous Google C++ program, one you've all interacted with, and my compilations were taking about 45 minutes on our huge distributed compile cluster.|author=Rob Pike|date=25 June 2012 |author-link=Rob Pike}} The problem is largely solved by precompiled headers in modern compilers or using the module system that was added in C++20; C++23 exposes the functionality of standard library through the standard modules.{{cite web|url=http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0592r3.html|title=To boldly suggest an overall plan for C++23|author=Ville Voutilainen}}

Global format state of <iostream>

C++ , unlike C , relies on a global format state. This fits very poorly together with exceptions, when a function must interrupt the control flow, after an error but before resetting the global format state. One fix for this is to use resource acquisition is initialization (RAII), which is implemented in the Boost{{Cite web|url=https://www.boost.org/doc/libs/1_60_0/libs/io/doc/ios_state.html|title=I/O Stream-State Saver Library - 1.60.0|website=www.boost.org}} libraries and part of the C++ Standard Library.

uses static constructors which causes overhead if included, even if the library is not used.{{Cite web|url=https://llvm.org/docs/CodingStandards.html|title=LLVM Coding Standards — LLVM 12 documentation|website=llvm.org}} Another source of bad performance is the misuse of std::endl instead of \n when doing output, as it also calls .flush(). C++ is by default synchronized with which can cause performance problems in command-line i/o intensive applications. Shutting it off can improve performance but forces giving up some ordering guarantees.

Here follows an example where an exception interrupts the function before std::cout can be restored from hexadecimal to decimal. The error number in the catch statement will be written out in hexadecimal which probably is not what one wants:

  1. include
  2. include

int main() {

try {

std::cout << std::hex

<< 0xFFFFFFFF << '\n';

// std::bad_alloc will be thrown here:

std::vector vector(0xFFFFFFFFFFFFFFFFull);

std::cout << std::dec; // Never reached

// (using scopes guards would have fixed that issue

// and made the code more expressive)

}

catch (const std::exception& e) {

std::cout << "Error number: " << 10 << '\n'; // Not in decimal

}

}

It is even acknowledged by some members of the C++ standards body{{Cite web|url=http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4412.html|title=N4412: Shortcomings of iostreams|website=open-std.org|access-date=3 May 2016}} that is an aging interface that eventually needs to be replaced.

C++20 added std::format that eliminated the global formatting state and addressed other issues in iostreams.{{Cite web|url=http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r10.html|title=P0645: Text Formatting|website=open-std.org|access-date=20 May 2021}} For example, the catch clause can now be written as

std::cout << std::format("Error number: {}\n", 10);

which is not affected by the stream state. The design of std::format permits more performant implementations, comparable or even faster (because of locale independence) to that of printf.

C++23 added std::print and std::println which are both formatted, now no longer relying on direct calls to std::cout and std::cerr and not relying on repetitive string concatenations.

Iterators

The philosophy of the Standard Template Library (STL) embedded in the C++ Standard Library is to use generic algorithms in the form of templates using iterators. Early compilers optimized small objects such as iterators poorly, which Alexander Stepanov characterized as the "abstraction penalty", although modern compilers optimize away such small abstractions well.{{cite web|url=http://www.open-std.org/jtc1/sc22/wg21/docs/D_3.cpp|title=Stepanov Benchmark|author=Alexander Stepanov|quote=The final number printed by the benchmark is a geometric mean of the performance degradation factors of individual tests. It claims to represent the factor by which you will be punished by your compiler if you attempt to use C++ data abstraction features. I call this number "Abstraction Penalty." As with any benchmark it is hard to prove such a claim; some people told me that it does not represent typical C++ usage. It is, however, a noteworthy fact that majority of the people who so object are responsible for C++ compilers with disproportionately large Abstraction Penalty.}} The interface using pairs of iterators to denote ranges of elements has also been criticized.{{cite web|url=http://accu.org/content/conf2009/AndreiAlexandrescu_iterators-must-go.pdf|title=Iterators Must Go|author=Andrei Alexandrescu}}{{cite web|url=http://dconf.org/2015/talks/alexandrescu.pdf|title=Generic Programming Must Go|author=Andrei Alexandrescu}} The C++20 standard library's introduction of ranges should solve this problem.{{Cite web|url=https://en.cppreference.com/w/cpp/ranges|title=Ranges library (C++20) - cppreference.com|website=en.cppreference.com}}

One big problem is that iterators often deal with heap allocated data in the C++ containers and become invalid if the data is independently moved by the containers. Functions that change the size of the container often invalidate all iterators pointing to it, creating dangerous cases of undefined behavior.{{cite book|title=Effective STL|author=Scott Meyers|quote=Given all that allocation, deallocation, copying, and destruction. It should not stun you to learn that these steps can be expensive. Naturally, you don't want to perform them any more frequently than you have to. If that doesn't strike you as natural, perhaps it will when you consider that each time these steps occur, all iterators, pointers, and references into the vector or string are invalidated. That means that the simple act of inserting an element into a vector or string may also require updating other data structures that use iterators, pointers, or references into the vector or string being expanded.}}{{cite web|url=http://www.angelikalanger.com/Conferences/Slides/CppInvalidIterators-DevConnections-2002.pdf|title=Invalidation of STL Iterators|author=Angelika Langer}} Here is an example where the iterators in the for loop get invalidated because of the std::string container changing its size on the heap:

  1. include
  2. include

int main() {

std::string text = "One\nTwo\nThree\nFour\n";

// Let's add an '!' where we find newlines

for (auto it = text.begin(); it != text.end(); ++it) {

if (*it == '\n') {

// it =

text.insert(it, '!') + 1;

// Without updating the iterator this program has

// undefined behavior and will likely crash

}

}

std::cout << text;

}

Uniform initialization syntax

The C++11 uniform initialization syntax and std::initializer_list share the same syntax which are triggered differently depending on the internal workings of the classes. If there is a std::initializer_list constructor then this is called. Otherwise the normal constructors are called with the uniform initialization syntax. This can be confusing for beginners and experts alike.{{cite web|url=http://scottmeyers.blogspot.de/2015/09/thoughts-on-vagaries-of-c-initialization.html|title=Thoughts on the Vagaries of C++ Initialization|author=Scott Meyers|date=7 September 2015 }}

  1. include
  2. include

int main() {

int integer1{10}; // int

int integer2(10); // int

std::vector vector1{10, 0}; // std::initializer_list

std::vector vector2(10, 0); // std::size_t, int

std::cout << "Will print 10\n" << integer1 << '\n';

std::cout << "Will print 10\n" << integer2 << '\n';

std::cout << "Will print 10,0,\n";

for (const auto& item : vector1) {

std::cout << item << ',';

}

std::cout << "\nWill print 0,0,0,0,0,0,0,0,0,0,\n";

for (const auto& item : vector2) {

std::cout << item << ',';

}

}

Exceptions

There have been concerns that the zero-overhead principle{{cite web|url=http://www.stroustrup.com/ETAPS-corrected-draft.pdf|title=Foundations of C++|author=Bjarne Stroustrup}} is not compatible with exceptions. Most modern implementations have a zero performance overhead when exceptions are enabled but not used, but do have an overhead during exception handling and in binary size due to the need to unwind the call stack. Many compilers support disabling exceptions from the language to save the binary overhead. Exceptions have also been criticized for being unsafe for state-handling. This safety issue has led to the invention of the RAII idiom,{{sfn|Stroustrup|1994|loc=16.5 Resource Management, pp. 388–89}} which has proven useful beyond making C++ exceptions safe.

Encoding of string literals in source-code

C++ string literals, like those of C, do not consider the character encoding of the text within them: they are merely a sequence of bytes, and the C++ string class follows the same principle. Although source code can (since C++11) request an encoding for a literal, the compiler does not attempt to validate that the chosen encoding of the source literal is "correct" for the bytes being put into it, and the runtime does not enforce character encoding. Programmers who are used to other languages such as Java, Python or C# which try to enforce character encodings often consider this to be a defect of the language.

The example program below illustrates the phenomenon.

  1. include
  2. include

// note that this code is no longer valid in C++20

int main() {

// all strings are declared with the UTF-8 prefix

// file encoding determines the encoding of å and Ö

std::string auto_enc = u8"Vår gård på Öland!";

// this text is well-formed in both ISO-8859-1 and UTF-8

std::string ascii = u8"Var gard pa Oland!";

// explicitly use the ISO-8859-1 byte-values for å and Ö

// this is invalid UTF-8

std::string iso8859_1 = u8"V\xE5r g\xE5rd p\xE5 \xD6land!";

// explicitly use the UTF-8 byte sequences for å and Ö

// this will display incorrectly in ISO-8859-1

std::string utf8 = u8"V\xC3\xA5r g\xC3\xA5rd p\xC3\xA5 \xC3\x96land!";

std::cout << "byte-count of automatically-chosen, [" << auto_enc

<< "] = " << auto_enc.length() << '\n';

std::cout << "byte-count of ASCII-only [" << ascii << "] = " << ascii.length()

<< '\n';

std::cout << "byte-count of explicit ISO-8859-1 bytes [" << iso8859_1

<< "] = " << iso8859_1.length() << '\n';

std::cout << "byte-count of explicit UTF-8 bytes [" << utf8

<< "] = " << utf8.length() << '\n';

}

Despite the presence of the C++11 'u8' prefix, meaning "Unicode UTF-8 string literal", the output of this program actually depends on the source file's text encoding (or the compiler's settings - most compilers can be told to convert source files to a specific encoding before compiling them). When the source file is encoded using UTF-8, and the output is run on a terminal that's configured to treat its input as UTF-8, the following output is obtained:

byte-count of automatically-chosen, [Vår gård på Öland!] = 22

byte-count of ASCII-only [Var gard pa Oland!] = 18

byte-count of explicit ISO-8859-1 bytes [Vr grd p land!] = 18

byte-count of explicit UTF-8 bytes [Vår gård på Öland!] = 22

The output terminal has stripped the invalid UTF-8 bytes from display in the ISO-8859 example string. Passing the program's output through a Hex dump utility will reveal that they are still present in the program output, and it is the terminal application that removed them.

However, when the same source file is instead saved in ISO-8859-1 and re-compiled, the output of the program on the same terminal becomes:

byte-count of automatically-chosen, [Vr grd p land!] = 18

byte-count of ASCII-only [Var gard pa Oland!] = 18

byte-count of explicit ISO-8859-1 bytes [Vr grd p land!] = 18

byte-count of explicit UTF-8 bytes [Vår gård på Öland!] = 22

One proposed solution is to make the source encoding reliable across all compilers.

See also

{{div col|colwidth=30em}}

{{div col end}}

References

{{Reflist}}

=Works cited=

  • {{Cite book | isbn = 0-201-54330-3 | title = The Design and Evolution of C++ | last = Stroustrup | first = Bjarne | author-link = Bjarne Stroustrup | year = 1994 | publisher = Addison-Wesley }}

Further reading

{{refbegin}}

  • {{cite book|author=Peter Seibel|title=Coders at Work: Reflections on the Craft of Programming|publisher=Apress|date=2009|isbn=978-1430219484}}

{{refend}}