- PVSM.RU - https://www.pvsm.ru -
Когда-то я собеседовался на должность C++ разработчика в одну приличную и даже известную контору. Опыт у меня тогда уже кое-какой был, я даже назывался ведущим разработчиком у тогдашнего своего работодателя. Но на вопросы о том, знаком ли я такими вещами, как DRY, KISS, YAGNI, NIH, раз за разом мне приходилось отвечать «Нет».
Собеседование я с треском провалил, конечно. Но упомянутые аббревиатуры потом загуглил и запомнил. По мере чтения тематических статей и книг, подготовок к собеседованиям и просто общения с коллегами я узнавал больше новых вещей, забывал их, снова гуглил и разбирался. Пару месяцев назад кто-то из коллег небрежно упомянул в рабочем чате IIFE в контексте C++. Я, как тот дед в анекдоте, чуть с печки не свалился и опять полез в гугл.
Тогда-то я и решил составить (в первую очередь для себя) шпаргалку по аббревиатурам, которые полезно знать C++ разработчику. Это не значит, что они относятся только к C++, или что это все-все-все понятия из C++ (об идиомах языка можно тома писать). Нет, это только реально встречавшиеся мне в работе и на собеседованиях понятия, обычно выражаемые в виде аббревиатур. Ну и я пропустил совсем уж тривиальные вещи вроде LIFO, FIFO, CRUD, OOP, GCC и MSVC.
Тем не менее аббревиатур набралось порядочно, поэтому шпаргалку я разделил на 2 части: сильно характерные для C++ и более общеупотребительные. Когда это было уместно, я группировал понятия вместе, иначе — просто перечислял по алфавиту. В общем, большого смысла в их порядке нет.
Базовые вещи: [1]
• ODR [2]
• POD [3]
• POF [4]
• PIMPL [5]
• RAII [6]
• RTTI [7]
• STL [8]
• UB [9]
Тонкости языка: [10]
• ADL [11]
• CRTP [12]
• CTAD [13]
• EBO [14]
• IIFE [15]
• NVI [16]
• RVO и NRVO [17]
• SFINAE [18]
• SBO, SOO, SSO [19]
One Definition Rule. Правило одного определения. Упрощенно означает следующее:
Компилятор легко отловит нарушение ODR в рамках единицы трансляции. Но он ничего не сможет сделать, если правило нарушается в масштабе программы — хотя бы потому, что компилятор обрабатывает по одной единице трансляции за раз.
Гораздо больше нарушений может найти линковщик, но, строго говоря, он не обязан этого делать (т. к. по Стандарту тут UB [9]) и что-то может пропустить. К тому же процесс поиска нарушений ODR на этапе линковки имеет квадратичную сложность, а сборка C++ кода и так не быстрая.
В итоге главным ответственным за соблюдение этого правила (особенно в масштабе программы) является сам разработчик. И да — нарушить ODR на масштабе программы могут только сущности с внешней линковкой; те, что с внутренней (т. е. определенные в анонимных неймспейсах), в этом карнавале не участвуют.
Почитать еще: раз (англ.) [20], два (англ.) [21]
Plain Old Data. Простая структура данных. Самое простое определение: это такая структура, которую можно как есть, в бинарном виде отправить в/получить из C библиотеки. Или, что то же самое, правильно скопировать простым memcpy
.
От Стандарта к Стандарту полное определение менялось в деталях. В новейшем на текущий момент C++17 POD определяется, как
Тривиальный класс (trivial class):
Класс со стандартным устройством (standard layout class):
struct A {};
struct B : A {};
struct C : A{};
struct D : B, C {};
В прочем, в C++20 понятия POD типа уже не будет, останутся только тривиальный тип и тип со стандартным устройством.
Почитать еще: раз (рус.) [22], два (англ.) [23], три (англ.) [24]
Plain Old Function. Простая функция в стиле C. Упоминается в Стандарте до C++14 включительно только в контексте обработчиков сигналов. Требования к ней такие:
try-catch
, например)std::atomic_init
, std::atomic_fetch_add
и т. п.)
Только такие функции, имеющие к тому же C линковку (extern "C"
), разрешается Стандартом использовать в качестве обработчиков сигналов. Поддержка других функций зависит от компилятора.
В C++17 понятие POF исчезает, вместо него появляется безопасное в смысле сигналов вычисление (signal-safe evaluation). В таких вычислениях запрещены:
new
и delete
dynamic_cast
thread_local
сущностиЕсли обработчик сигнала делает что-то из вышеперечисленного, Стандарт обещает UB [9].
Почитать еще: раз (англ.) [25]
Pointer To Implementation. Указатель на реализацию. Классическая идиома в C++, так же известная как d-pointer, opaque pointer, compilation firewall. Заключается в том, что все закрытые методы, поля и прочие детали реализации некоего класса выделяются в отдельный класс, а в исходном классе остаются только публичные методы (т. е. интерфейс) и указатель на экземпляр этого нового отдельного класса. Например:
class Foo
{
public:
Foo();
~Foo();
void doThis();
int doThat();
private:
class Impl;
std::unique_ptr<Impl> pImpl_;
};
#include "foo.h"
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();
}
Зачем это надо, т. е. преимущества:
Цена, т. е. недостатки:
void Foo::doThis() const
{
pImpl_->doThis(); // cosnt method
pImpl_->doSmthElse(); // non-const method
}
Некоторые из этих недостатков устранимы, но цена — дальнейшее усложнение кода и введение дополнительных уровней абстракции (см. FTSE во второй части).
Почитать еще: раз (рус.) [26], два (рус.) [27], три (англ.) [28]
Resource Acquisition Is Initialization. Захват ресурса есть инициализация. Смысл этой идиомы в том, что удержание некоторого ресурса длится в течении жизни соответствующего объекта. Захват ресурса происходит в момент создания/инициализации объекта, освобождение — в момент разрушения/финализации этого же объекта.
Как ни странно (в первую очередь для программистов на C++), эта идиома используется и в других языках, даже в тех, где существует сборщик мусора. В Java это try-с-ресурсами
, в Python – оператор with
, в C# – директива using
, в Go – defer
. Но именно в C++ с его абсолютно предсказуемой жизнью объектов RAII вписывается особенно органично.
В C++ обычно ресурс захватывается в конструкторе и освобождается в деструкторе. Например, умные указатели так управляют памятью, файловые потоки — файлами, локи мьютексов — мьютексами. Прелесть в том, что не зависимо от того, как происходит выход из блока (scope) – нормально ли через любую из точек выхода, или было брошено исключение — управляющий ресурсом объект, созданный в этом блоке, будет уничтожен, а ресурс — освобожден. Т.е. помимо инкапсуляции RAII в C++ еще и помогает обеспечивать безопасность в смысле исключений.
Ограничения, куда же без них. Деструкторы в C++ не возвращают значений и категорически не должны бросать исключения. Соответственно, если освобождение ресурса сопровождается тем или другим, придется реализовать дополнительную логику в деструкторе управляющего объекта.
Почитать еще: раз (рус.) [29], два (англ.) [30]
Run-Time Type Information. Идентификация типа во время исполнения. Это механизм, позволяющий получить информацию о типе объекта или выражения во время выполнения. Существует и в других языках, а в C++ он используется для:
dynamic_cast
typeid
и type_info
Важное ограничение: RTTI использует таблицу виртуальных функций, и, следовательно, работает только для полиморфных типов (виртуального деструктора достаточно). Важное пояснение: dynamic_cast
и typeid
не всегда используют RTTI, поэтому работают и для неполиморфных типов. Например, для динамического приведения ссылки на потомка к ссылке на предка RTTI не нужен, вся информация доступна во время компиляции.
RTTI не дается бесплатно, пусть немного, но он отрицательно влияет на производительность и размер потребляемой памяти (отсюда частый совет не использовать dynamic_cast
из-за его медлительности). Поэтому компиляторы, как правило, позволяют отключить RTTI. GCC и MSVC обещают, что на корректности перехвата исключений это не скажется.
Почитать еще: раз (рус.) [31], два (англ.) [32]
Standard Template Library. Стандартная библиотека шаблонов. Часть стандартной библиотеки C++, предоставляющая обобщенные контейнеры, итераторы, алгоритмы и вспомогательные функции.
Не смотря на известное имя, STL никогда так не называлась в Стандарте. Из разделов Стандарта к STL однозначно можно отнести Containers library, Iterators library, Algorithm library и частично General utilities library.
В описании вакансий часто можно встретить 2 отдельных требования — знание C++ и знакомство с STL. Я никогда этого не понимал, ведь STL — неотъемлемая часть языка с первого Стандарта 1998 года.
Почитать еще: раз (рус.) [33], два (англ.) [34]
Undefined Behavior. Неопределенное поведение. Это поведение в тех ошибочных случаях, для которых Стандарт не имеет никаких требований. Многие из них явно перечислены в Стандарте как приводящие к UB. К ним, например, относятся:
Результат UB зависит от всего подряд — и от версии компилятора, и от погоды на Марсе. Причем этим результатом может быть что угодно: и ошибка компиляции, и корректное выполнение, и аварийное завершение. Неопределенное поведение — зло, от него необходимо избавляться.
С другой стороны, неопределенное поведение не стоит путать с неуточняемым поведением (unspecified behavior). Неуточняемое поведение — это корректное поведение корректной программы, но которое с разрешения Стандарта зависит от компилятора. И компилятор не обязан документировать его. Например, это порядок вычисления аргументов функции или детали реализации std::map
.
Ну и тут же можно вспомнить про поведение, зависящее от реализации (implementation-defined behavior). От неуточняемого отличается наличием документации. Пример: размер std::size_t
.
Почитать еще: раз (рус.) [35], два (рус.) [36], три (англ.) [37]
Argument-Dependent Lookup. Поиск, зависящий от аргументов. Он же поиск Кёнига — в честь Andrew Koenig. Это набор правил для разрешения неквалифицированных имен функций (т. е. имен без оператора ::
), дополнительный к обычному разрешению имен. Упрощенно: имя функции ищется в пространствах имен, относящихся к ее аргументам (это пространство, содержащее тип аргумента, сам тип, если это класс, все его предки и т.п.).
#include <iostream>
namespace N
{
struct S {};
void f(S) { std::cout << "f(S)" << std::endl; };
}
int main()
{
N::S s;
f(s);
}
Функция f
найдена в пространстве имен N
только потому, что ее аргумент принадлежит этому пространству.
Даже банальный std::cout << "Hello World!n"
использует ADL, т. к. std::basic_stream::operator<<
не перегружен для const char*
. Но первым аргументом этого оператора является std::basic_stream
, и компилятор ищет и находит подходящую перегрузку в пространстве имен std
.
Некоторые детали: ADL не применяется, если обычный поиск нашел объявление члена класса, или объявление функции в текущем блоке без использования using
, или объявление не функции и не шаблона функции. Или есть имя функции указано в скобках (пример выше не скомпилируется с (f)(s)
; придется писать (N::f)(s);
).
Иногда ADL заставляет использовать полные квалифицированные имена функций там, где это, казалось бы, излишне.
namespace N1
{
struct S {};
void foo(S) {};
}
namespace N2
{
void foo(N1::S) {};
void bar(N1::S s) { foo(s); }
}
Почитать еще: раз (англ.) [38], два (англ.) [39], три (англ.) [40]
Curiously Recurring Template Pattern. Странно рекурсивный шаблон. Суть шаблона в следующем:
Проще привести пример:
template <class T>
struct Base {};
struct Derived : Base<Derived> {};
CRTP — яркий пример статического полиморфизма. Базовый класс предоставляет интерфейс, классы-наследники — реализацию. Но в отличие от обычного полиморфизма здесь нет накладных расходов на создание и использование таблицы виртуальных функций.
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();
}
При правильном использовании T
всегда является потомком Base
, поэтому для приведения достаточно static_cast
. Да, в данном случае базовый класс знает интерфейс потомка.
Еще одной частой областью использования CRTP является расширение (или сужение) функциональности наследных классов (то, что в некоторых языках называется mixin). Пожалуй самые известные примеры:
struct Derived : singleton<Derived> { … }
struct Derived : private boost::noncopyable<Derived> { … }
struct Derived : std::enable_shared_from_this<Derived> { … }
struct Derived : counter<Derived> { … }
— подсчет числа созданных и/или существующих объектовНедостатки, или, скорее, требующие внимания моменты:
template <typename T>
struct Base {};
struct Derived1 : Base<Derived1> {};
struct Derived2 : Base<Derived1> {};
Но можно добавить защиту:
private:
Base() = default;
friend T;
Почитать еще: раз (рус.) [41], два (англ.) [42]
Class Template Argument Deduction. Автоматический вывод типа параметра шаблона класса. Это новая возможность из C++17. Раньше автоматически выводились только типы переменных (auto
) и параметры шаблонов функций, из-за чего и возникли вспомогательные функции типа std::make_pair
, std::make_tuple
и т. п. Теперь они по большей части не нужны, т. к. компилятор способен автоматически вывести и параметры шаблонов классов:
std::pair p{1, 2.0}; // -> std::pair<int, double>
auto lck = std::lock_guard{mtx}; // -> std::lock_guard<std::mutex>
CTAD – новая возможность, ей еще развиваться и развиваться (С++20 уже обещает улучшения). Пока же ограничения таковы:
std::pair<double> p{1, 2}; // ошибка
std::tuple<> t{1, 2, 3}; // ошибка
template <class T, class U>
using MyPair = std::pair<T, U>;
MyPair p{1, 2}; // ошибка
template <class T>
struct Wrapper {};
template <>
struct Wrapper<int>
{
Wrapper(int) {};
};
Wrapper w{5}; // ошибка
template <class T>
struct Foo
{
template <class U>
struct Bar
{
Bar(T, U) {};
};
};
Foo::Bar x{ 1, 2.0 }; // ошибка
Foo<int>::Bar x{1, 2.0}; // OK
template <class T>
struct Collection
{
Collection(std::size_t size) {};
};
Collection c{5}; // ошибка
В некоторых случаях помогут явные правила вывода, которые должны быть объявлены в том же блоке, что и шаблон класса.
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
Почитать еще: раз (рус.) [43], два (англ.) [44]
Empty Base Optimization. Оптимизация пустого базового класса. Так же может называться Empty Base Class Optimization (EBCO).
Как известно, в C++ размер объекта любого класса не может быть нулем. Иначе сломается вся арифметика указателей, т. к. по одному адресу будет возможно разметить сколько угодно разных объектов. Поэтому даже объекты пустых классов (т. е. классов без единого нестатического поля) имеют какой-то ненулевой размер, который зависит от компилятора и ОС и обычно равен 1.
Таким образом память зря тратится на все объекты пустых классов. Но не объекты их потомков, т. к. в данном случае Стандарт явно делает исключение. Компилятору разрешено не выделять память под пустой базовый класс и экономить таким образом не только 1 байт пустого класса, а все 4 (зависит от платформы), т. к. есть еще и выравнивание.
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
Но т. к. по одному адресу все-таки не могут размещаться разные объекты одного типа, EBO не сработает, если:
struct Empty {};
struct Empty2 : Empty {};
struct Foo : Empty, Empty2
{
int i;
};
std::cout << sizeof(Empty) << std::endl; // 1
std::cout << sizeof(Empty2) << std::endl; // 1
std::cout << sizeof(Foo) << std::endl; // 8
struct Empty {};
struct Foo : Empty
{
Empty e;
int i;
};
std::cout << sizeof(Empty) << std::endl; // 1
std::cout << sizeof(Foo) << std::endl; // 8
В случаях же когда объекты пустых классов являются нестатическими полями, никаких оптимизаций не предусмотрено (это пока, в C++20 появится атрибут [[no_unique_address]]
). Но тратить по 4 байта (или сколько компилятору надо) на каждое такое поле обидно, поэтому можно самостоятельно «схлопнуть» объекты пустых классов с первым непустым нестатическим полем.
struct Empty1 {};
struct Empty2 {};
template <class Member, class ... Empty>
struct EmptyOptimization : Empty ...
{
Member member;
};
struct Foo
{
EmptyOptimization<int, Empty1, Empty2> data;
};
Странно, но в этом случае размер Foo получается разным у разных компиляторов, у MSVC 2019 это 8, у GCC 8.3.0 это 4. Но в любом случае увеличение числа пустых классов на размер Foo
не влияет.
Почитать еще: раз (англ.) [45], два (англ.) [46]
Immediately-Invoked Function Expression. Немедленно вызываемое функциональное выражение. Вообще это идиома в JavaScript, откуда Джейсон Тёрнер (Jason Turner) ее и позаимствовал вместе с названием. По факту это просто создание и немедленный вызов лямбды:
const auto myVar = [&] {
if (condition1())
{
return computeSomeComplexStuff();
}
return condition2() ? computeSonethingElse() : DEFAULT_VALUE;
} ();
Зачем это надо? Ну например, как в приведенном коде для того, чтобы инициализировать константу результатом нетривиального вычисления и не засорить при этом область видимости лишними переменными и функциями.
Почитать еще: раз (англ.) [47], два (англ.) [48]
Non-Virtual Interface. Невиртуальный интерфейс. Согласно этой идиоме открытый интерфейс класса не должен содержать виртуальных функций. Все виртуальные функции делаются закрытыми (максимум защищенными) и вызываются внутри открытых невиртуальных.
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
{
}
};
Зачем это надо:
Плата за использование NVI – некоторое разбухание кода, возможное снижение производительности (из-за одного дополнительного вызова метода) и повышенная подверженность проблеме хрупкого базового класса (см. FBC во второй части).
Почитать еще: раз (англ.) [49], два (англ.) [50]
(Named) Return Value Optimization. Оптимизация (именованного) возвращаемого значения. Это частный случай разрешенного Стандартом copy elision – компилятор может опустить ненужные копирования временных объектов, даже если их конструкторы и деструкторы имеют явные побочные эффекты. Такая оптимизация допустима, когда функция возвращает объект по значению (два других разрешенных случая copy elision – это выброс и поимка исключений).
Foo bar()
{
return Foo();
}
int main()
{
auto f = Foo();
}
Без RVO здесь был бы создан временный объект Foo
в функции bar
, потом через конструктор копирования из него был бы создан еще один временный объект в функции main
(чтобы получить результат bar
), и только потом был бы создан объект f
и ему было бы присвоено значение второго временного объекта. RVO избавляется от всех этих копирований и присваиваний, и функция bar
создает непосредственно f
.
Происходит это примерно так: функция main
выделяет в своем фрейме стека место под объект f
. Функция bar
(работающая уже в своем фрейме), получает доступ к этой памяти, выделенной в предыдущем фрейме и создает там нужный объект.
NRVO отличается от RVO тем, что делает такую же оптимизацию, но не когда объект создается в выражении return
, а когда возвращается ранее созданный в функции объект.
Foo bar()
{
Foo result;
return result;
}
Несмотря на кажущуюся небольшой разницу, NRVO реализовать гораздо сложнее, и потому она не работает во многих случая. Например, если функция возвращает глобальный объект или один из своих аргументов, или если у функции есть несколько точек выхода, и через них возвращаются разные объекты — NRVO не применится.
Foo bar(bool condition)
{
if (condition)
{
Foo f1;
return f1;
}
Foo f2;
return f2;
}
Практически все компиляторы давно поддерживают RVO. Степень поддержки же NRVO может варьироваться от компилятора к компилятору и от версии к версии.
RVO и NRVO – это всего лишь оптимизации. И хотя копирующие конструктор и оператор присваивания не вызываются, они должны быть у класса объекта. Правила немного поменялись в C++17: теперь RVO не считается copy elision, стала обязательной, и соответствующие конструктор и оператор присваивания не нужны.
Внимание: (N)RVO в константных выражениях — скользкая тема. До C++14 включительно об этом ничего не сказано, C++17 требует RVO в таких выражениях, а грядущий C++20 – запрещает.
Пара слов о связи с семантикой перемещения. Во-первых, (N)RVO все-таки эффективнее, т.к. не надо вызывать конструктор перемещения и деструктор. Во-вторых, если вместо result
из той же функции возвращать std::move(result)
, то NRVO гарантированно не сработает. Перефразируя Стандарт: RVO применяется к prvalue, NRVO – к lvalue, a std::move(result)
– это xvalue.
Почитать еще: раз (англ.) [51], два (англ.) [52], три (англ.) [53]
Substitution Failure Is Not An Error. Неудачная подстановка — не ошибка. SFINAE — это особенность процесса инстанциации шаблонов — функций и классов — в С++. Суть в том, что если некий шаблон не получается инстанциировать, это не считается ошибкой, если есть другие варианты. Например, упрощенно алгоритм выбора наиболее подходящей перегрузки функций работает так:
Так вот SFINAE происходит на втором шаге: если перегрузка получается инстанцированием шаблона функции, но компилятор не смог вывести типы сигнатуры функции, то такая перегрузка не считается ошибкой, а молча отбрасывается (даже без предупреждения). И аналогично для классов.
SFINAE может применяться для многих вещей, например, для подсчета длины списка инициализации или для подсчета бит в числе. Но чаще всего с ее помощью худо-бедно эмулируется рефлексия, т. е. определяется, если ли у класса метод с определенной сигнатурой.
#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
}
Появившийся в C++17 static if
может в некоторых случаях заменить SFINAE, а ожидаемые в C++20 концепты чуть ли вообще не сделают ее ненужной. Посмотрим.
Почитать еще: раз (рус.) [54], два (англ.) [55], три (англ.) [56]
Small Buffer/Object/String Optimization. Оптимизация малых буферов/объектов/строк. Иногда встречается SSO в значении Small Size Optimization, но очень редко, поэтому будем считать, что SSO – это про строки. SBO и SOO – просто синонимы, а SSO – наиболее известный частный случай.
Все структуры данных, использующие динамическую память, безусловно занимают и какое-то место и на стеке. Хотя бы для того, чтобы хранить указатель на кучу. И суть этих оптимизаций в том, чтобы для достаточно малых объектов не запрашивать память у кучи (что относительно затратно), а размещать их в уже выделенном пространстве стека.
Например, std::string можно было бы реализовать так:
class string
{
char* begin_;
size_t size_;
size_t capacity_;
};
Размер такого класса у меня получается 24 байта (зависит от компилятора и платформы). Т.е. строки не длиннее 24 символов можно было бы размещать на стеке. На самом не до 24, конечно, т. к. надо как-то различать размещение на стеке и в куче. Но вот простейший способ для коротких строк до 8 символов (размер тот же — 24 байта):
class string
{
union Buffer
{
char* begin_;
char local_[8];
};
Buffer buffer_;
size_t _size;
size_t _capacity;
};
Помимо отсутствия аллокаций в куче, есть еще одно преимущество — высокая степень локальности данных. Массив или вектор таких оптимизированных объектов будет действительно занимать лишь непрерывный кусок памяти.
Почти все реализации std::string
используют SSO и как минимум некоторые реализации std::function
. А вот std::vector
никогда не оптимизируется таким образом, т. к. Стандарт требует, чтобы std::swap
для двух векторов не вызывала копирования или присваивания их элементов, и чтобы все валидные итераторы оставались валидными. SBO не позволит выполнить эти требования (для std::string
их нет). Зато boost::container::small_vector
, как легко догадаться, использует SBO.
Почитать еще: раз (англ.) [57], два (англ.) [58]
Если я что-то упустил или где-то ошибся — пишите в комментариях. Только помните, пожалуйста, что здесь перечислены только аббревиатуры, непосредственно относящиеся к C++. Для прочих, но не менее полезных, будет отдельный пост.
Автор: Михаил Зинин
Источник [59]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/razrabotka/332342
Ссылки в тексте:
[1] Базовые вещи:: #Basics
[2] ODR: #ODR
[3] POD: #POD
[4] POF: #POF
[5] PIMPL: #PIMPL
[6] RAII: #RAII
[7] RTTI: #RTTI
[8] STL: #STL
[9] UB: #UB
[10] Тонкости языка:: #Cobwebs
[11] ADL: #ADL
[12] CRTP: #CRTP
[13] CTAD: #CTAD
[14] EBO: #EBO
[15] IIFE: #IIFE
[16] NVI: #NVI
[17] RVO и NRVO: #RVO
[18] SFINAE: #SFINAE
[19] SBO, SOO, SSO: #SBO
[20] раз (англ.): https://en.cppreference.com/w/cpp/language/definition
[21] два (англ.): https://akrzemi1.wordpress.com/2016/11/28/the-one-definition-rule/
[22] раз (рус.): https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D1%81%D1%82%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85
[23] два (англ.): https://en.cppreference.com/w/cpp/named_req/PODType
[24] три (англ.): https://stackoverflow.com/questions/4178175/what-are-aggregates-and-pods-and-how-why-are-they-special/7189821#7189821
[25] раз (англ.): https://wiki.sei.cmu.edu/confluence/display/cplusplus/MSC54-CPP.+A+signal+handler+must+be+a+plain+old+function
[26] раз (рус.): https://habr.com/ru/post/311038/
[27] два (рус.): https://habr.com/ru/post/118010/
[28] три (англ.): https://arne-mertz.de/2019/01/the-pimpl-idiom/
[29] раз (рус.): https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D1%80%D0%B5%D1%81%D1%83%D1%80%D1%81%D0%B0_%D0%B5%D1%81%D1%82%D1%8C_%D0%B8%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F
[30] два (англ.): https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Resource_Acquisition_Is_Initialization
[31] раз (рус.): https://ru.wikipedia.org/wiki/%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D0%B8%D0%B4%D0%B5%D0%BD%D1%82%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D1%8F_%D1%82%D0%B8%D0%BF%D0%B0_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85
[32] два (англ.): https://blog.panicsoftware.com/dynamic_cast-and-typeid-as-non-rtti-tools/
[33] раз (рус.): https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82%D0%BD%D0%B0%D1%8F_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA%D0%B0_%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D0%BE%D0%B2
[34] два (англ.): https://www.geeksforgeeks.org/the-c-standard-template-library-stl/
[35] раз (рус.): https://habr.com/ru/post/216189/
[36] два (рус.): http://alenacpp.blogspot.com/2005/08/unspecified-behavior-undefined.html
[37] три (англ.): https://en.cppreference.com/w/cpp/language/ub
[38] раз (англ.): https://en.cppreference.com/w/cpp/language/adl
[39] два (англ.): https://www.modernescpp.com/index.php/c-core-guidelines-argument-dependent-lookup-or-koenig-lookup
[40] три (англ.): https://quuxplusone.github.io/blog/2019/04/26/what-is-adl/
[41] раз (рус.): https://habr.com/ru/post/210894/
[42] два (англ.): https://www.fluentcpp.com/2017/05/12/curiously-recurring-template-pattern/
[43] раз (рус.): https://habr.com/ru/post/461963/
[44] два (англ.): https://accu.org/index.php/journals/2465
[45] раз (англ.): https://en.cppreference.com/w/cpp/language/ebo
[46] два (англ.): http://www.informit.com/articles/article.aspx?p=31473&seqNum=2
[47] раз (англ.): https://www.bfilipek.com/2016/11/iife-for-complex-initialization.html
[48] два (англ.): https://articles.emptycrate.com/2014/12/16/complex_object_initialization_optimization_with_iife_in_c11.html
[49] раз (англ.): https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface
[50] два (англ.): http://www.gotw.ca/publications/mill18.htm
[51] раз (англ.): https://www.ibm.com/developerworks/community/blogs/5894415f-be62-4bc0-81c5-3956e82276f3/entry/RVO_V_S_std_move?lang=en
[52] два (англ.): https://shaharmike.com/cpp/rvo/
[53] три (англ.): https://source.coveo.com/2018/11/07/interaction-between-move-semantic-and-copy-elision-in-c++/
[54] раз (рус.): https://habr.com/ru/post/205772/
[55] два (англ.): https://www.bfilipek.com/2016/02/notes-on-c-sfinae.html
[56] три (англ.): https://www.fluentcpp.com/2018/05/15/make-sfinae-pretty-1-what-value-sfinae-brings-to-code/
[57] раз (англ.): https://riptutorial.com/cplusplus/example/31654/small-object-optimization
[58] два (англ.): https://shaharmike.com/cpp/std-string/
[59] Источник: https://habr.com/ru/post/470265/?utm_source=habrahabr&utm_medium=rss&utm_campaign=470265
Нажмите здесь для печати.