C ++ abbreviation cheat sheet and more. Part 1: C ++

Once I was interviewed for the position of C ++ developer in one decent and even well-known office. I already had some experience then, I was even called a leading developer at my employer at that time. But when asked if I knew such things as DRY, KISS, YAGNI, NIH, I had to answer “No” over and over again.



I failed miserably, of course. But then the above abbreviations were googled and remembered. As I read thematic articles and books, prepared for interviews, and just talked with colleagues, I learned more new things, forgot them, googled again and understood. A couple of months ago, one of my colleagues casually mentioned in the IIFE working chat in the context of C ++. I, like that grandfather in a joke, almost fell off the stove and again got into Google.







It was then that I decided to compose (primarily for myself) a cheat sheet for abbreviations that are useful for a C ++ developer to know. This does not mean that they apply only to C ++, or that they are all-all-all concepts from C ++ (you can write volumes about language idioms). No, these are only concepts that I really encountered in work and interviews, usually expressed in the form of abbreviations. Well, I missed absolutely trivial things like LIFO, FIFO, CRUD, OOP, GCC and MSVC.



Nevertheless, the abbreviations came up decently, so I divided the cheat sheet into 2 parts: strongly characteristic of C ++ and more common. When it was appropriate, I grouped the concepts together, otherwise I simply listed them alphabetically. In general, there isn’t much sense in their order.



Basic things:

ODR

POD

POF

PIMPL

RAII

RTTI

STL

UB



Subtleties of the language:

ADL

CRTP

CTAD

EBO

IIFE

NVI

RVO and NRVO

SFINAE

SBO, SOO, SSO



UPDATE:

CV

LTO

PCH

PGO

SEH / VEH

TMP

VLA



Basic things



ODR



One Definition Rule. The rule of one definition. Simplified means the following:





The compiler will easily catch an ODR violation within a broadcast unit. But he won’t be able to do anything if the rule is violated at the program level — if only because the compiler processes one translation unit at a time.



The linker can find much more violations, but, strictly speaking, he is not obliged to do this (because, according to the Standard, UB is here) and he can miss something. In addition, the process of searching for ODR violations at the linking stage has quadratic complexity, and the assembly of C ++ code is not so fast.



As a result, the main responsibility for observing this rule (especially at the program level) is the developer himself. And yes - only entities with an external link can violate ODR on a program scale; those from the inside (i.e. defined in anonymous namespaces) do not participate in this carnival.



Read more: one time (English) , two (English)



Pod



Plain Old Data. Simple data structure. The simplest definition: this is such a structure that you can, as it is, in binary form send to / receive from the C library. Or, which is the same thing, correctly copy with simple memcpy



.



From Standard to Standard, the full definition has changed in detail. The latest C ++ 17 POD currently defines how





Trivial class





Class with a standard device (standard layout class):





However, in C ++ 20 there will no longer be a POD type concept, only a trivial type and a type with a standard device will remain.



Read more: one (Russian) , two (English) , three (English)



POF



Plain Old Function. A simple C-style function. Mentioned in the Standard prior to C ++ 14 inclusive only in the context of signal handlers. The requirements for it are:





Only such functions, which also have a C link ( extern "C"



), are permitted by the Standard to be used as signal handlers. Support for other functions depends on the compiler.



In C ++ 17, the concept of POF disappears; instead, a signal-safe evaluation appears in the sense of signals . In such calculations are prohibited:





If the signal handler does any of the above, the Standard promises UB .



Read more: time (English)



PIMPL



Pointer To Implementation. Pointer to implementation. The classic idiom in C ++, also known as d-pointer, opaque pointer, compilation firewall. It consists in the fact that all private methods, fields, and other implementation details of a certain class are allocated into a separate class, and only public methods (i.e., an interface) and a pointer to an instance of this new separate class remain in the original class. For example:



foo.hpp
 class Foo { public: Foo(); ~Foo(); void doThis(); int doThat(); private: class Impl; std::unique_ptr<Impl> pImpl_; };
      
      







foo.cpp
 #include "foo.hpp" class Foo::Impl { // implementation }; Foo::Foo() : pImpl_(std::make_unique<Impl>()) {} Foo::~Foo() = default; void Foo::doThis() { pImpl_->doThis(); } int Foo::doThat() { return pImpl_->doThat(); }
      
      







Why is this necessary, i.e. advantages:





Price, i.e., disadvantages:





Some of these shortcomings are removable, but the price is further complicating the code and introducing additional levels of abstraction (see FTSE ).



Read more: one (Russian) , two (Russian) , three (English)



RAII



Resource Acquisition Is Initialization. Capturing a resource is initialization. The meaning of this idiom is that the retention of a certain resource lasts throughout the life of the corresponding object. The capture of the resource occurs at the time of creation / initialization of the object, the release - at the time of destruction / finalization of the same object.



Oddly enough (primarily for C ++ programmers), this idiom is used in other languages, even those with a garbage collector. In Java, these are try--



, in Python the with



statement, in C # the using



directive, in Go the defer



. But it is in C ++ with its absolutely predictable life of objects that RAII fits in especially organically.



In C ++, a resource is usually captured in the constructor and freed in the destructor. For example, smart pointers control memory this way, file streams manage files, mutex locks use mutexes. The beauty is that no matter how the block exits (scope) - is it normal through any of the exit points, or an exception was thrown - the resource control object created in this block will be destroyed, and the resource will be freed. Those. In addition to encapsulating RAII in C ++, it also helps ensure security in the sense of exceptions.



Limitations, where without them. Destructors in C ++ do not return values ​​and categorically should not throw exceptions. Accordingly, if the release of the resource is accompanied by one or another, you will have to implement additional logic in the destructor of the control object.



Read more: one (Russian) , two (English)



RTTI



Run-Time Type Information. Type identification at runtime. This is a mechanism for obtaining information about the type of an object or expression at run time. It exists in other languages, but in C ++ it is used for:





An important limitation: RTTI uses a table of virtual functions, and therefore works only for polymorphic types (a virtual destructor is enough). An important explanation: dynamic_cast



and typeid



do not always use RTTI , and therefore work for non-polymorphic types. For example, to dynamically cast a reference to a descendant to a link to an ancestor, RTTI is not needed; all information is available at compile time.



RTTI is not free, albeit a little, but it negatively affects the performance and size of the memory consumed (hence the frequent advice not to use dynamic_cast



due to its slowness). Therefore, compilers, as a rule, allow you to disable RTTI . GCC and MSVC promise that this will not affect the correctness of catching exceptions.



Read more: one (Russian) , two (English)



STL



Standard Template Library. Standard template library. Part of the C ++ standard library that provides generic containers, iterators, algorithms, and helper functions.



Despite its well-known name, STL has never been so called in the Standard. Of the sections of the Standard, the STL can clearly be attributed to the Containers library, Iterators library, Algorithm library, and partially the General utilities library.



In job descriptions, you can often find 2 separate requirements - knowledge of C ++ and familiarity with STL . I never understood this, because STL is an integral part of the language since the first 1998 Standard.



Read more: one (Russian) , two (English)



UB



Undefined Behavior. Undefined behavior. This behavior is in those error cases for which the Standard has no requirements. Many of these are explicitly listed in the Standard as leading to UB . These include, for example:





The result of UB depends on everything in a row - both on the compiler version and on the weather on Mars. And this result can be anything: a compilation error, and correct execution, and crash. Indefinite behavior is evil, it is necessary to get rid of it.



Undefined behavior, on the other hand, should not be confused with unspecified behavior . Unspecified behavior is the correct behavior of the correct program, but which, with the permission of the Standard, depends on the compiler. And the compiler is not required to document it. For example, this is the order in which the arguments of a function are evaluated or the implementation details of std::map



.



Well, here you can recall the implementation-defined behavior. From unspecified differs in the availability of documentation. Example: the compiler is free to make the type std::size_t



any size, but must indicate which one.



Read more: one (Russian) , two (Russian) , three (English)



The subtleties of language



ADL



Argument-Dependent Lookup. Argument-dependent search. He is the search for Koenig - in honor of Andrew Koenig. This is a set of rules for resolving unqualified function names (i.e., names without the ::



operator), in addition to the usual name resolution. Simply put: the name of a function is looked up in the namespaces related to its arguments (this is the space containing the type of the argument, the type itself, if it is a class, all its ancestors, etc.).



Simplest example
 #include <iostream> namespace N { struct S {}; void f(S) { std::cout << "f(S)" << std::endl; }; } int main() { N::S s; f(s); }
      
      





The function f



found in the namespace N



only because its argument belongs to this space.



Even the trivial std::cout << "Hello World!\n"



uses ADL , std::basic_stream::operator<<



not overloaded for const char*



. But the first argument to this statement is std::basic_stream



, and the compiler searches and finds a suitable overload in the std



.



Some details: ADL is not applicable if a regular search found a declaration of a class member, or a function declaration in the current block without using using



, or a declaration of neither a function nor a function template. Or if the function name is indicated in parentheses (the example above does not compile with (f)(s)



; you will have to write (N::f)(s);



).



Sometimes ADL forces you to use fully qualified function names where it would seem unnecessary.



For example, this code does not compile
 namespace N1 { struct S {}; void foo(S) {}; } namespace N2 { void foo(N1::S) {}; void bar(N1::S s) { foo(s); } }
      
      







Read more: one (English) , two (English) , three (English)



CRTP



Curiously Recurring Template Pattern. Strange recursive pattern. The essence of the template is as follows:





It’s easier to give an example:



 template <class T> struct Base {}; struct Derived : Base<Derived> {};
      
      





CRTP is a prime example of static polymorphism. The base class provides an interface; the derived classes provide an implementation. But unlike ordinary polymorphism, there is no overhead for creating and using a table of virtual functions.



Example
 template <typename T> struct Base { void action() const { static_cast<T*>(this)->actionImpl(); } }; struct Derived : Base<Derived> { void actionImpl() const { ... } }; template <class Arg> void staticPolymorphicHandler(const Arg& arg) { arg.action(); }
      
      





When used correctly, T



always a descendant of Base



, so static_cast



is enough to static_cast



. Yes, in this case, the base class knows the descendant interface.



Another common area of ​​use for CRTP is to extend (or narrow down) the functionality of inherited classes (something called mixin in some languages). Perhaps the most famous examples:





Disadvantages, or rather, moments requiring attention:







Read more: one (Russian) , two (English)



CTAD



Class Template Argument Deduction. Automatically inferring the type of a class template parameter. This is a new feature from C ++ 17. Previously, only variable types ( auto



) and function template parameters were automatically displayed, which is why auxiliary functions like std::make_pair



, std::make_tuple



, etc. std::make_tuple



. Now they are mostly not needed, because the compiler able to automatically display the parameters of class templates:



 std::pair p{1, 2.0}; // -> std::pair<int, double> auto lck = std::lock_guard{mtx}; // -> std::lock_guard<std::mutex>
      
      





CTAD is a new opportunity, it still needs to evolve and evolve (C ++ 20 already promises improvements). In the meantime, the restrictions are as follows:





In some cases, explicit inference rules that should be declared in the same block as the class template will help.



Example
 template <class T> struct Collection { template <class It> Collection(It from, It to) {}; }; Collection c{v.begin(), v.end()}; //  template <class It> Collection(It, It)->Collection<typename std::iterator_traits<It>::value_type>; Collection c{v.begin(), v.end()}; //  OK
      
      







Read more: one (Russian) , two (English)



EBO



Empty Base Optimization. Optimization of an empty base class. Also called Empty Base Class Optimization (EBCO).



As you know, in C ++, the size of an object of any class cannot be zero. Otherwise, all the arithmetic of pointers will break, because at one address it will be possible to mark as many different objects as you like. Therefore, even objects of empty classes (i.e., classes without a single non-static field) have some non-zero size, which depends on the compiler and OS and is usually equal to 1.



Thus, memory is wasted in vain on all objects of empty classes. But not the objects of their descendants, because in this case the Standard explicitly makes an exception. The compiler is allowed not to allocate memory for an empty base class and thus save not only 1 byte of the empty class, but all 4 (depending on the platform), since there is also alignment.



Example
 struct Empty {}; struct Foo : Empty { int i; }; std::cout << sizeof(Empty) << std::endl; // 1 std::cout << sizeof(Foo) << std::endl; // 4 std::cout << sizeof(int) << std::endl; // 4
      
      





But since different objects of the same type cannot be placed at the same address, the EBO will not work if:





In cases where objects of empty classes are non-static fields, no optimizations are provided (for now, the attribute [[no_unique_address]]



will appear in C ++ 20). But spending 4 bytes (or how much the compiler needs) for each such field is a shame, so you can “collapse” objects of empty classes with the first non-empty non-static field on your own.



Example
 struct Empty1 {}; struct Empty2 {}; template <class Member, class ... Empty> struct EmptyOptimization : Empty ... { Member member; }; struct Foo { EmptyOptimization<int, Empty1, Empty2> data; };
      
      





Strange, but in this case, the Foo size is different for different compilers, for MSVC 2019 it is 8, for GCC 8.3.0 it is 4. But in any case, increasing the number of empty classes does not affect the Foo



size.


Read more: one time (English) , two (English)



IIFE



Immediately-Invoked Function Expression. Functional expression called immediately. In general, this is an idiom in JavaScript, from where Jason Turner borrowed it from the name. In fact, it's just creating and immediately calling a lambda:



 const auto myVar = [&] { if (condition1()) { return computeSomeComplexStuff(); } return condition2() ? computeSonethingElse() : DEFAULT_VALUE; } ();
      
      





Why is this necessary? Well, for example, as in the above code in order to initialize a constant with the result of a non-trivial calculation and not clog the scope with unnecessary variables and functions.



Read more: one time (English) , two (English)



NVI



Non-Virtual Interface. Non-virtual interface. According to this idiom, an open class interface should not contain virtual functions. All virtual functions are made private (maximum protected) and called inside open non-virtual.



Example
 class Base { public: virtual ~Base() = default; void foo() { // check precondition fooImpl(); // check postconditions } private: virtual void fooImpl() = 0; }; class Derived : public Base { private: void fooImpl() override { } };
      
      





Why is this necessary:





The fee for using NVI is some swelling of the code, possible performance degradation (due to one additional method call) and increased susceptibility to the problem of a fragile base class (see FBC ).



Read more: one time (English) , two (English)



RVO and NRVO



(Named) Return Value Optimization. Optimizing the (named) return value. This is a special case of copy elision permitted by the Standard - the compiler can omit unnecessary copies of temporary objects, even if their constructors and destructors have obvious side effects. Such optimization is permissible when the function returns an object by value (two other permitted cases of copy elision are the throwing and catching of exceptions).



Example
 Foo bar() { return Foo(); } int main() { auto f = bar(); }
      
      





Without RVO , a temporary object Foo



in the function would be created here bar



, then through the copy constructor another temporary object in the function would be created from it main



(to get the result bar



), and only then would the object be created f



and the value of the second temporary object would be assigned to it. RVO gets rid of all these copying and assignment, and the function bar



creates directly f



.



This happens approximately like this: a function main



allocates a space for an object in its stack frame f



. A function bar



(already working in its frame) gains access to this memory allocated in the previous frame and creates the desired object there.



NRVO is different fromRVO does the same optimization, but not when the object is created in the expression return



, but when the object previously created in the function is returned.



Example
 Foo bar() { Foo result; return result; }
      
      





Despite the seemingly small difference, NRVO is much more difficult to implement, and therefore it does not work in many cases. For example, if a function returns a global object or one of its arguments, or if a function has several exit points and different objects are returned through them, NRVO will not apply.



NRVO doesn't work here
 Foo bar(bool condition) { if (condition) { Foo f1; return f1; } Foo f2; return f2; }
      
      





Almost all compilers have long supported RVO . The degree of support for NRVO can vary from compiler to compiler and from version to version.



RVO and NRVO are just optimizations. And although copying the constructor and assignment operator are not called, they should be in the class of the object. The rules have changed a bit in C ++ 17: now RVO is not considered copy elision, it has become mandatory, and the corresponding constructor and assignment operator are not needed.



Note: (N) RVO in constant terms is a slippery topic. Until C ++ 14 inclusive, nothing was said about it, C ++ 17 requires RVO in such expressions, and the upcoming C ++ 20 - prohibits.



A few words about the connection with the semantics of displacement. Firstly, (N) RVO is still more effective, because no need to call the move constructor and destructor. Secondly, if instead result



of returning from the same function std::move(result)



, NRVO is guaranteed to not work. To paraphrase Standard: RVO applies to prvalue, NRVO applies to lvalue, a std::move(result)



is xvalue.



Read more: one (English) , two (English) , three (English)



SFINAE



Substitution Failure Is Not An Error. Failed substitution is not a mistake. SFINAE is a feature of the instantiation process of templates — functions and classes — in C ++. The bottom line is that if a certain template cannot be instantiated, this is not considered an error if there are other options. For example, a simplified algorithm for choosing the most appropriate function overload works like this:



  1. The name of the function is resolved - the compiler searches for all functions with the given name in all considered namespaces (see ADL ).
  2. Inappropriate functions are discarded - not the number of arguments, there is no necessary conversion of argument types, it was not possible to derive types for the function template, etc.
  3. (viable functions), . — .


So SFINAE occurs in the second step: if the overload is obtained by instantiating the function template, but the compiler could not infer the types of the signature of the function, then this overload is not considered an error, but is silently discarded (even without warning). And similarly for classes.



SFINAE can be used for many things, for example, for counting the length of an initialization list or for counting bits in a number. But most often, with its help, reflection is emulated at the very least, that is, it is determined if the class has a method with a certain signature.



Example
 #include <iostream> #include <type_traits> #include <utility> template <class, class = void> struct HasToString : std::false_type {}; //    ,      //   -    ,  //     —  ,    ,   template <class T> struct HasToString<T, std::void_t<decltype(&T::toString)>> : std::is_same<std::string, decltype(std::declval<T>().toString())> {}; struct Foo { std::string toString() { return {}; } }; int main() { std::cout << HasToString<Foo>::value << std::endl; // 1 std::cout << HasToString<int>::value << std::endl; // 0 }
      
      





What appeared in C ++ 17 static if



may in some cases replace SFINAE , and the concepts expected in C ++ 20 will almost make it unnecessary. We'll see.



Read more: one (Russian) , two (English) , three (English)



SBO, SOO, SSO



Small Buffer / Object / String Optimization. Optimization of small buffers / objects / lines. Sometimes SSO is used in the meaning of Small Size Optimization, but it is very rare, so we assume that SSO is about strings. SBO and SOO are simply synonyms, and SSO is the most famous special case.



All data structures using dynamic memory certainly occupy some place on the stack as well. At least in order to store a pointer to a bunch. And the essence of these optimizations is not to request memory from the heap for relatively small objects (which is relatively expensive), but to place them in the already allocated stack space.



For example, std :: string could be implemented like this:



Example
 class string { char* begin_; size_t size_; size_t capacity_; };
      
      





The size of this class, I get 24 bytes (depending on the compiler and platform). Those.strings no longer than 24 characters could be placed on the stack. Actually, not until 24, of course, since it is necessary to somehow distinguish between placement on the stack and on the heap. But here is the simplest way for short lines up to 8 characters (the same size - 24 bytes):



Example
 class string { union Buffer { char* begin_; char local_[8]; }; Buffer buffer_; size_t _size; size_t _capacity; };
      
      





In addition to the lack of allocations on the heap, there is another advantage - a high degree of data locality. An array or vector of such optimized objects will really only occupy a continuous piece of memory.



Almost all implementations std::string



use SSO and at least some implementations std::function



. But it is std::vector



never optimized in this way, because the Standard requires that std::swap



for two vectors it does not cause copying or assignment of their elements, and that all valid iterators remain valid. SBO will not allow to fulfill these requirements (for std::string



they are not). But boost::container::small_vector



, as you might guess, uses SBO .



Read more: time (English) ,two



UDPATE



Thanks to PyerK for this additional list of abbreviations.

CV



Qualifiers like const and volatile. const



means that the object / variable cannot be modified, an attempt to do this will result either in an error at compile time or in UB at run time. volatile



means that the object / variable can change regardless of the actions of the program (for example, some microcontroller fills writes something to memory), and the compiler should not optimize access to it. Access to an volatile



object not through a volatile



link or pointer also results in UB .



Read more: one (Russian) , two (English) , three (Russian)



LTO



Link Time Optimization. Link optimization. As the name implies, this optimization occurs during linking, i.e., after compilation. The linker can do what the compiler did not dare to: make some functions inline, throw out unused code and data. Increases link time, of course.



Read more: time (English)



PCH



Precompiled Headers. Precompiled headers. Often used, but rarely modified header files are compiled once and saved in the internal compiler format. Thus, reassembling a project will take less time, sometimes much less.



Read more: time (rus.)



Pgo



Profile-Guided Optimization. Optimization based on profiling results. This is a program optimization method, but not through static code analysis, but through test program launches and collecting real statistics. For example, branching and calling virtual functions in this way can be optimized.



Read more: time (rus.)



Seh / veh



Structured / Vectored Exception Handling. This is an MSVC extension for exception and error handling. Unlike standard try-catch



SEH uses its own keywords: __try



, __except



, __finally



, catches and handles are not explicitly thrown exceptions, and such things as access to an invalid memory stack overflow due to infinite recursion, call a pure virtual function, etc... VEH It does not catch every error explicitly, but creates a global chain of error handlers.



Read more: time (English)



Tmp



Template Meta-Programming. Template metaprogramming. Metaprogramming is when one program creates another as a result of its work. Templates in C ++ implement such metaprogramming. The template compiler generates the required number of classes or functions. It is known that TMP in C ++ is Turing-complete, i.e., any function can be implemented on it.



Read more: time (rus.)



VLA



Variable-Length Arrays. Arrays of variable length. Those. arrays whose length is unknown at the compilation stage:

 void foo(int n) { int array[n]; }
      
      





The C ++ standard does not allow this. Which is somewhat strange, because they exist in pure C since the C99 standard. And are supported by some C ++ compilers as an extension.



Read more: time (rus.)



PS



If I missed something or was mistaken somewhere - write in the comments. Just remember, please, that only abbreviations directly related to C ++ are listed here. For others, but no less useful, there will be a separate post.



The second part of



All Articles