Criticism of C++
Although C++ is one of the most widespread programming languages,[1] C++ (the language and its compilers) has received various criticisms. Some arguments include that it is overly complex[2] and fundamentally flawed.[3][opinion] Among the critics have been: Rob Pike,[2] Joshua Bloch, Linus Torvalds,[3] 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 forfeiting the readability benefits of common style and idioms. Joshua Bloch, a software engineer who developed several Java platform features, called C++ "pushed well beyond its complexity threshold", and criticised the language for its overabundance of features leading to different users using different subsets of the language, which he believed would decrease programmer and code portability.[4] This sentiment was echoed by Donald Knuth, who stated that it was impossible for him to write portable code unless he avoided "all exotic features", while also calling the language "too baroque" for his taste.[5][6] Knuth in 1993 mentioned of Edsger Dijkstra that to think of programming in C++ "would make him physically ill".
Ken Thompson, who was a colleague of Bjarne Stroustrup at Bell Labs, critiqued the language for its design by committee and aggregation of ideas that he deemed mutually exclusive, calling the language "just the union of everything that came along".[7][4]
Undefined behavior
Undefined behavior refers to behavior at runtime[8] that the C++ standard does not guarantee how the program will continue upon the violation of such a rule, rendering the entire program meaningless.[9] Undefined behavior exists to allow compilers to make optimizations on the assumption that certain behaviors never happen. Undefined behavior is criticized for security vulnerability and being hard to avoid.[10] Examples of undefined behavior include dereferencing a null pointer, freeing memory more than once, use of a pointer or memory after deallocation, out-of-bounds accessing of an array index, as well as various uses of const_cast and reinterpret_cast.[9]
In C++26, with the introduction of erroneous behavior and safety improvements, some cases previously considered undefined behavior are now detected as compile errors. These include reading uninitialized variables[9], returning a reference to a temporary variable[11], and deleting a pointer to an incomplete type.[12]
Here are some code examples that cause undefined behavior:
import std;
double obtainSomeNumber() noexcept {
return 3.14;
}
const double& mischievousFunction() {
// undefined behavior prior to C++26 (returning a reference to a temporary object)
return obtainSomeNumber();
}
int main() {
float number = 2.718f;
float* addr = &number;
addr = nullptr;
float val = *addr; // undefined behavior (dereferencing a nullptr)
char* addr2 = reinterpret_cast<char*>(0xB80000);
char segfault = *addr2; // undefined behavior (segmentation fault, trying to access random addresses)
int dummy;
// undefined behavior prior to C++26 (reading an uninitialized variable)
std::println("{}", dummy);
}
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 must be preprocessed and then parsed, as well as the headers that it includes, transitively, leading to redundant work compiling headers that may even be shared between translation units.[13] 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.[13]
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.[14]
Rob Pike, the creator of the Go programming language, complained about compilation times in C++ during his work at Google.[15]
Global format state of <iostream>
C++ <iostream>, unlike C <stdio.h>, relies on a global format state. This fits very poorly together with exceptions[further explanation needed], 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[16] libraries and part of the C++ Standard Library.
<iostream> uses static constructors which may cause overhead if included, even if the library is not used.[17] Another source of bad performance is the use of std::endl instead of \n when doing output, as it also calls .flush(). C++ <iostream> is by default synchronized with <stdio.h> 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:
import std;
using std::bad_alloc;
using std::vector;
int main() {
try {
std::cout << std::hex << 0xFFFFFFFF << '\n';
vector<int> v(0xFFFFFFFFFFFFFFFFull); // std::bad_alloc will be thrown here
std::cout << std::dec; // Never reached; issue could be solved using scope guarding
} catch (const bad_alloc& e) {
std::cout << "Error number: " << 10 << '\n'; // Not in decimal
}
}
It is even acknowledged by some members of the C++ standards body[18] that <iostream> 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.[19] 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.[19]
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, simplifying the example further:
std::println("Error number: {}", 10);
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.[20] The interface using pairs of iterators to denote ranges of elements has also been criticized.[21][22] The C++20 standard library's introduction of the std::ranges library solves this problem.[23]
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.[24][25] 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:
import std;
using std::string;
int main() {
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::print("{}", 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.[17] Uniform initialization also prevents narrowing conversions like precision loss during double to int conversion (for example, initializations like int x{5.5}; are not allowed).[26] Furthermore, the uniform initialization syntax eliminates the problem called most vexing parse, which is about the interpretation of the compiler about declaration is a function or an object.[26] This can be confusing for beginners and experts alike.[27][28] [not in citation given]
using std::vector;
// Two initializations of int (other than int x = 10;)
int a{10};
int b(10);
// Declared with initializer list
// Produces a vector containing {10, 0}
vector<int> v1{10, 0};
// Declared with constructor with (size_t, int)
// Produces a vector of 10 0's
vector<int> v2(10uz, 0);
Exceptions
There have been various criticisms made against C++ exceptions, some of which include that they violate the zero-overhead principle[29][not in citation given], that they increase binary size[17], that they prevent compiler optimizations, or that exception unwinding is "effectively" single-threaded.[30] 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. [who?] This safety issue has led to the invention of the RAII idiom,[31] which has proven useful beyond making C++ exceptions safe.
Encoding of string literals in source-code
This section may contain misleading parts. (March 2026) |
This section needs to be updated. In particular: The example code won't be compiled since C++20, and this section never mentions new things like char8_t, char16_t, char32_t and the proposal P2295R6 which makes utf-8 as a portable source encoding among the compilers since C++23. (March 2026) |
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 [which?] 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 [by whom?] of the language.[citation needed]
The example program below illustrates the phenomenon:Template:Outdated inline
#include <iostream>
#include <string>
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[non sequitur] (or the compiler's settings - most compilers can be told to convert source files to a specific encoding before compiling them).
This section contains information of unclear or questionable importance or relevance to the article's subject matter. (March 2026) (Learn how and when to remove this template message) |
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.[citation needed]
Ecosystem and standard library
- C++ lacks a central ecosystem, similar to that of Rust, leading to challenges with interoperability, and has also been suggested to hinder further development.[32] CMake, often seen as the "de-facto standard" build tool[citation needed], has often been criticised for its verbosity and complexity[33], compared to the simpler Cargo build system and package manager for Rust.[citation needed] Proposals for ecosystem standardization have been made to potentially rectify such a problem.[32]
- C++ does not separate the standard library into finer namespaces. While this has gradually changed, such as with the introduction of namespaces
std::chrono&std::this_thread(C++11),std::filesystem(C++17),std::ranges&std::numbers(C++20), andstd::meta(C++26), traditionally the C++ standard library placed all of its symbols in namespacestdwithout further division. This is unlike some other languages, such as Rust or Java, whose standard libraries have namespaces such asstd::collectionsandstd::iofor Rust, andjava.utilandjava.iofor Java. - C++ naming conventions are not unified.[34] The C++ Standard Library and Boost libraries (following the so-called "Core Guidelines" which are maintained by Bjarne Stroustrup and Herb Sutter but not officially ratified by the ISO C++ standards committee[35]) use snake_case, however camelCase and PascalCase (similar to Java) remains equally prevalent in newer or more modern code bases (such as those of Google, Clang/LLVM or Meta).[36][37][38]
See also
References
- ↑ "Stack Overflow Developer Survey 2021". Stack Overflow. Retrieved 2021-12-28.
- ↑ 2.0 2.1 "Google executive frustrated by Java, C++ complexity - Google, software, application development, Development tools, Languages and standards, Rob Pike". CIO. Retrieved 2021-12-28.
- ↑ 3.0 3.1 "C++ (Al Viro; Linus Torvalds; Theodore Ts'o)". yarchive.net. Retrieved 2021-12-28.
- ↑ 4.0 4.1 "C++ in Coders at Work". 16 October 2009. Archived from the original on 10 November 2017. Retrieved 9 November 2017. Unknown parameter
|url-status=ignored (help) - ↑ "An Interview with Donald Knuth". Dr. Dobb's Journal (Interview). Interviewed by Jack Woehr. April 1996. Archived from the original on 30 April 2003. Unknown parameter
|url-status=ignored (help) - ↑ "(La)TeX Navigator". Archived from the original on 20 November 2017. Retrieved 10 November 2017. Unknown parameter
|url-status=ignored (help) - ↑ Peter Seibel (16 September 2009). Coders at Work: Reflections on the Craft of Programming. Apress. pp. 475–476. ISBN 978-1-4302-1948-4. Retrieved 9 November 2017. Search this book on
- ↑ Raymond Chen (2 August 2024). "The difference between undefined behavior and ill-formed C++ programs". devblogs.microsoft.com. Microsoft Corporation.
- ↑ 9.0 9.1 9.2 cppreference.com. "Undefined behavior". cppreference.com. cppreference.com. Retrieved 4 April 2026.
- ↑ Tiemoko Ballo (6 July 2025). "High Assurance Rust: Developing Secure and Robust Software - On Undefined Behavior". highassurance.rs.
- ↑ Bartlomiej Filipek (20 January 2025). "Improving Code Safety in C++26: Managers and Dangling References". cppstories.com. C++ Stories.
- ↑ Rainer Grimm (22 September 2025). "Small Safety Improvements in the C++26 Core Language". modernescpp.com. MC++.
- ↑ 13.0 13.1 LLVM Developer Group. "Modules - Problems with the current model". clang.llvm.org. LLVM Developer Group. Retrieved 3 April 2026.
- ↑ Ville Voutilainen. "To boldly suggest an overall plan for C++23".
- ↑ Rob Pike (25 June 2012). "Less is exponentially more".
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.
- ↑ "I/O Stream-State Saver Library - 1.60.0". www.boost.org.
- ↑ 17.0 17.1 17.2 "LLVM Coding Standards — LLVM 12 documentation". llvm.org.
- ↑ "N4412: Shortcomings of iostreams". open-std.org. Retrieved 3 May 2016.
- ↑ 19.0 19.1 "P0645: Text Formatting". open-std.org. Retrieved 20 May 2021.
- ↑ Alexander Stepanov. "Stepanov Benchmark".
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.
- ↑ Andrei Alexandrescu. "Iterators Must Go" (PDF).
- ↑ Andrei Alexandrescu. "Generic Programming Must Go" (PDF).
- ↑ "Ranges library (C++20) - cppreference.com". en.cppreference.com.
- ↑ Scott Meyers. Effective STL.
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.
Search this book on
- ↑ Angelika Langer. "Invalidation of STL Iterators" (PDF).
- ↑ 26.0 26.1 Uniform Initialization, intel.com
- ↑ Bjarne Stroustrup (2 February 2008). "Uniform initialization design choices" (PDF). stroustrup.com. Texas A&M University.
- ↑ Scott Meyers (7 September 2015). "Thoughts on the Vagaries of C++ Initialization".
- ↑ Bjarne Stroustrup. "Foundations of C++" (PDF).
- ↑ Thomas Neumann (7 February 2022). "C++ exceptions are becoming more and more problematic" (PDF). open-std.org. WG21.
- ↑ Stroustrup 1994, 16.5 Resource Management, pp. 388–89.
- ↑ 32.0 32.1 René Ferdinand Rivera Morell (14 February 2023). "C++ Ecosystem International Standard". open-std.org. WG21.
- ↑ Roscoe A. Bartlett, Bill Hoffman, Brad King (2022). "A new CMake Scripting Language?". osti.gov. Sandia National Laboratories.CS1 maint: Multiple names: authors list (link)
- ↑ "Boost Library Requirements and Guidelines". Retrieved 2015-08-13.
- ↑ Bjarne Stroustrup, Herb Sutter (8 July 2025). "C++ Core Guidelines". github.com. C++ Core Guidelines.
- ↑ Google LLC. "Google C++ Style Guide". google.github.io. Google LLC. Retrieved 18 January 2026.
- ↑ LLVM Developer Group (2 April 2026). "LLVM Coding Standards". llvm.org. LLVM Developer Group.
- ↑ POCO Project (2023). "CppCodingStyleGuide" (PDF). appinf.com. POCO Project.
Works cited
- Stroustrup, Bjarne (1994). The Design and Evolution of C++. Addison-Wesley. ISBN 0-201-54330-3. Search this book on

Further reading
- Peter Seibel (2009). Coders at Work: Reflections on the Craft of Programming. Apress. ISBN 978-1430219484. Search this book on

External links
- C++ FQA Lite by Yossi Kreinin
- "C++ The COBOL of the 90s" chapter 10 in The Unix Haters Group Book
- C++ in Coders at Work Excerpts from the book Coders at Work, by Peter Seibel
- DConf 2014: The Last Thing D Needs A video of a talk by Scott Meyers
- A Critique of C++ and Programming and Language Trends of the 1990s - 3rd Edition by Ian Joyner - 1996
This article "Criticism of C++" is from Wikipedia. The list of its authors can be seen in its historical and/or the page Edithistory:Criticism of C++. Articles copied from Draft Namespace on Wikipedia could be seen on the Draft Namespace of Wikipedia and not main one.
