Folha de dicas da abreviação de C ++ e muito mais. Parte 1: C ++

Uma vez fui entrevistado para o cargo de desenvolvedor de C ++ em um escritório decente e até bem conhecido. Eu já tinha alguma experiência na época, até fui chamado de desenvolvedor líder do meu empregador na época. Mas quando perguntado se eu sabia de coisas como DRY, KISS, YAGNI, NIH, tive que responder "Não" repetidamente.

Eu falhei miseravelmente, é claro. Mas as abreviações acima foram pesquisadas e lembradas. Ao ler artigos e livros temáticos, preparados para entrevistas e conversando com colegas, aprendi mais coisas novas, esqueci-as, pesquisei novamente no Google e entendi. Há alguns meses, um dos meus colegas mencionou casualmente no bate-papo de trabalho do IIFE no contexto de C ++. Como aquele avô em uma piada, quase caí do fogão e entrei novamente no Google.



Foi então que decidi compor (principalmente para mim) uma cábula para abreviações que são úteis para um desenvolvedor de C ++ saber. Isso não significa que eles se apliquem apenas ao C ++ ou que sejam conceitos todos do C ++ (você pode escrever volumes sobre idiomas da linguagem). Não, esses são apenas conceitos que eu realmente encontrei no trabalho e nas entrevistas, geralmente expressos na forma de abreviações. Bem, eu perdi coisas absolutamente triviais como LIFO, FIFO, CRUD, OOP, GCC e MSVC.

No entanto, as abreviaturas surgiram decentemente, então dividi a cábula em 2 partes: característica característica do C ++ e mais comum. Quando apropriado, agrupei os conceitos, caso contrário, simplesmente os listei em ordem alfabética. Em geral, não há muito sentido na ordem deles.

Coisas básicas:
ODR
POD
POF
PIMPL
RAII
RTTI
STL
UB

Sutilezas da língua:
ADL
CRTP
CTAD
EBO
IIFE
NVI
RVO e NRVO
SFINAE
SBO, SOO, SSO

ATUALIZAÇÃO:
CV
LTO
PCH
PGO
SEH / VEH
TMP
VLA

Coisas básicas


ODR


Regra de uma definição. A regra de uma definição. Simplificado significa o seguinte:

  • Dentro de uma única unidade de tradução, cada variável, função, classe etc. não pode ter mais de uma definição. Existem tantos anúncios quanto possível (exceto transferências sem um determinado tipo de base, que simplesmente não podem ser declarados sem a definição), mas não mais de uma definição. Menos possível se a entidade não for usada.
  • Durante todo o programa, cada função não linear e variável usada deve ter exatamente uma definição. Cada função embutida e variável usada deve ter uma definição em cada unidade de tradução.
  • Algumas entidades - por exemplo, classes, funções embutidas e uma variável, modelos, enumerações etc. - podem ter várias definições em um programa (mas não mais que uma em uma unidade de tradução). Na verdade, isso acontece quando o mesmo cabeçalho que contém uma classe totalmente implementada, por exemplo, é conectado a vários arquivos .cpp. Mas essas definições devem coincidir (simplifico bastante, mas a essência é essa). Caso contrário, será UB .

O compilador detectará facilmente uma violação de ODR dentro de uma unidade de tradução. Mas ele não poderá fazer nada se a regra for violada em uma escala de programa - apenas porque o compilador processa uma unidade de tradução por vez.

O vinculador pode encontrar muito mais violações, mas, estritamente falando, ele não é obrigado a fazer isso (porque, de acordo com o Padrão, a UB está aqui) e ele pode perder alguma coisa. Além disso, o processo de busca de violações de ODR no estágio de vinculação tem complexidade quadrática, e a montagem do código C ++ não é tão rápida.

Como resultado, a principal responsabilidade pelo cumprimento desta regra (especialmente no nível do programa) é o próprio desenvolvedor. E sim - apenas entidades com um link externo podem violar o ODR em uma escala de programa; aqueles de dentro (ou seja, definidos em namespaces anônimos) não participam deste carnaval.

Leia mais: uma vez (inglês) , duas (inglês)

Pod


Dados antigos simples. Estrutura de dados simples. A definição mais simples: essa é uma estrutura que você pode, como está, em formato binário, enviar / receber da biblioteca C. Ou, o que é a mesma coisa, copie corretamente com memcpy simples.

De padrão para padrão, a definição completa foi alterada em detalhes. O POD mais recente do C ++ 17 define atualmente como

  • tipo escalar
  • ou uma classe / estrutura / união que:
    - existe uma classe trivial
    - existe uma classe com um dispositivo padrão
    - não contém campos não estáticos não- POD
  • ou uma matriz desses tipos

Classe trivial:

  • tem pelo menos um não excluído:
    - construtor padrão
    - construtor de cópias
    - construtor em movimento
    - operador de atribuição de cópias
    - mover operador de atribuição
  • todos os construtores padrão que copiam e movem construtores e operadores de atribuição são triviais (simplificados - gerados pelo compilador) ou remotos
  • tem um destruidor não remoto trivial
  • todos os tipos de base e todos os campos de tipos de classe têm destruidores triviais
  • nenhum método virtual (incluindo destruidor)
  • nenhum tipo de base virtual

Classe com um dispositivo padrão (classe de layout padrão):

  • sem métodos virtuais
  • nenhum tipo de base virtual
  • nenhum campo de link não estático
  • todos os campos não estáticos têm o mesmo modificador de acesso (público / protegido / privado)
  • todos os campos não estáticos e classes base também são tipos com um dispositivo padrão
  • todos os campos não estáticos da própria classe e todos os seus ancestrais são declarados em uma única classe (ou seja, na própria classe ou em um dos ancestrais)
  • Ele não herda o mesmo tipo duas vezes, ou seja, não é assim:
     struct A {}; struct B : A {}; struct C : A{}; struct D : B, C {}; 
  • o tipo do primeiro campo não estático ou, se for uma matriz, o tipo de seu elemento não deve coincidir com nenhum dos tipos básicos (devido ao EBO obrigatório neste caso)

No entanto, no C ++ 20 não haverá mais um conceito de tipo POD , apenas o tipo trivial e o tipo com o dispositivo padrão permanecerão.

Leia mais: um (russo) , dois (inglês) , três (inglês)

POF


Função antiga simples. Uma função simples no estilo C. Mencionada no Padrão anterior ao C ++ 14, inclusive apenas no contexto de manipuladores de sinal. Os requisitos para isso são:

  • usa apenas coisas comuns a C e C ++ (ou seja, sem exceções e try-catch , por exemplo)
  • não causa direta ou indiretamente funções não POF , com exceção de operações atômicas sem blocos ( std::atomic_init , std::atomic_fetch_add , etc.)

Somente essas funções, que também possuem um link C ( extern "C" ), são permitidas pela Norma como manipuladores de sinal. O suporte para outras funções depende do compilador.

No C ++ 17, o conceito de POF desaparece, em vez de parecer uma avaliação segura do sinal. Em tais cálculos são proibidos:

  • chama todas as funções da biblioteca padrão, exceto atômica, sem bloqueio
  • chamadas new e delete
  • usando dynamic_cast
  • chamar a entidade thread_local
  • qualquer trabalho com exceções
  • inicialização de uma variável estática local
  • aguardando a conclusão da inicialização da variável estática

Se o manipulador de sinal fizer alguma das opções acima, o Padrão promete UB .

Leia mais: time (inglês)

PIMPL


Ponteiro para implementação. Ponteiro para implementação. O idioma clássico em C ++, também conhecido como ponteiro-d, ponteiro opaco, firewall de compilação. Consiste no fato de que todos os métodos privados, campos e outros detalhes de implementação de uma determinada classe são alocados em uma classe separada, e apenas métodos públicos (ou seja, uma interface) e um ponteiro para uma instância dessa nova classe separada permanecem na classe original. Por exemplo:

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(); } 


Por que isso é necessário, ou seja, vantagens:

  • Encapsulamento: os usuários da classe através da conexão do cabeçalho obtêm apenas o que precisam - uma interface pública. Se os detalhes da implementação mudarem, o código do cliente não precisará ser recompilado (consulte ABI ).
  • Tempo de compilação: como o cabeçalho público não sabe nada sobre a implementação, ele não inclui os muitos cabeçalhos necessários. Consequentemente, o número de cabeçalhos implicitamente conectados no código do cliente é reduzido. A busca por nomes e a resolução de sobrecargas também é simplificada, porque o cabeçalho público não contém membros privados (embora sejam privados, eles participam desses processos).

Preço, ou seja, desvantagens:

  • Mais pelo menos uma referência de ponteiro e mais uma chamada de função ao acessar métodos públicos.
  • O tamanho da classe de memória necessária é aumentado pelo tamanho do ponteiro.
  • Parte dessa memória (provavelmente maior) é alocada no heap, o que também afeta negativamente o desempenho.
  • A constância lógica pode ser facilmente violada. Por exemplo, esse código compila:

     void Foo::doThis() const { pImpl_->doThis(); // cosnt method pImpl_->doSmthElse(); // non-const method } 

Algumas dessas deficiências são removíveis, mas o preço está complicando ainda mais o código e introduzindo níveis adicionais de abstração (consulte FTSE ).

Leia mais: um (russo) , dois (russo) , três (inglês)

RAII


Aquisição de recursos é inicialização. Capturar um recurso é inicialização. O significado desse idioma é que a retenção de um determinado recurso dura toda a vida útil do objeto correspondente. A captura do recurso ocorre no momento da criação / inicialização do objeto, a liberação - no momento da destruição / finalização do mesmo objeto.

Por incrível que pareça (principalmente para programadores de C ++), esse idioma também é usado em outras linguagens, mesmo naquelas com um coletor de lixo. Em Java, é try-- , em Python a instrução with , em C # a diretiva using , em Go the defer . Mas é em C ++ com sua vida absolutamente previsível de objetos que o RAII se encaixa especialmente organicamente.

Em C ++, um recurso geralmente é capturado no construtor e liberado no destruidor. Por exemplo, ponteiros inteligentes controlam a memória dessa maneira, fluxos de arquivos gerenciam arquivos, mutex bloqueia mutexes. O bom é que, não importa como o bloco sai (escopo) - é normal através de qualquer um dos pontos de saída ou foi lançada uma exceção - o objeto de controle de recurso criado nesse bloco será destruído e o recurso será liberado. I.e. Além de encapsular o RAII no C ++, também ajuda a garantir a segurança no sentido de exceções.

Limitações, onde sem eles. Destruidores em C ++ não retornam valores e categoricamente não devem lançar exceções. Portanto, se a liberação do recurso for acompanhada por um ou outro, será necessário implementar lógica adicional no destruidor do objeto de controle.

Leia mais: uma vez (russo) , dois (inglês)

RTTI


Informações sobre o tipo de tempo de execução. Identificação do tipo em tempo de execução. Este é um mecanismo para obter informações sobre o tipo de um objeto ou expressão em tempo de execução. Existe em outros idiomas, mas em C ++ é usado para:

  • dynamic_cast
  • typeid e type_info
  • pegar exceção

Uma limitação importante: o RTTI usa uma tabela de funções virtuais e, portanto, funciona apenas para tipos polimórficos (um destruidor virtual é suficiente). Uma explicação importante: dynamic_cast e typeid nem sempre usam RTTI e, portanto, funcionam para tipos não polimórficos. Por exemplo, para converter dinamicamente um link para um descendente para um link para um ancestral, o RTTI não é necessário; todas as informações estão disponíveis no momento da compilação.

O RTTI não é gratuito, embora um pouco, mas afeta negativamente o desempenho e o tamanho da memória consumida (daí o conselho frequente de não usar o dynamic_cast por causa de sua lentidão). Portanto, os compiladores, como regra, permitem desativar o RTTI . O GCC e o MSVC prometem que isso não afetará a correção das exceções de captura.

Leia mais: uma vez (russo) , dois (inglês)

STL


Biblioteca de modelos padrão. Biblioteca de modelos padrão. Parte da biblioteca padrão C ++ que fornece contêineres genéricos, iteradores, algoritmos e funções auxiliares.

Apesar de seu nome bem conhecido, STL nunca foi assim chamado no Padrão. Nas seções do Padrão, o STL pode ser claramente atribuído à biblioteca Containers, Iterators, Algorithm Library e parcialmente à General utilities library.

Nas descrições de cargo, você pode encontrar 2 requisitos separados - conhecimento de C ++ e familiaridade com STL . Eu nunca entendi isso, porque STL é parte integrante da linguagem desde o primeiro padrão de 1998.

Leia mais: uma vez (russo) , dois (inglês)

UB


Comportamento indefinido. Comportamento indefinido. Esse comportamento ocorre nos casos de erro para os quais a Norma não possui requisitos. Muitos deles estão explicitamente listados no Padrão como levando ao UB . Isso inclui, por exemplo:

  • violação dos limites de uma matriz ou contêiner STL
  • uso de variável não inicializada
  • desreferenciando um ponteiro nulo
  • estouro inteiro assinado

O resultado do UB depende de tudo em uma linha - na versão do compilador e no clima em Marte. Além disso, esse resultado pode ser qualquer coisa: um erro de compilação, execução correta e falha. Comportamento indefinido é mau, é necessário se livrar dele.

O comportamento indefinido, por outro lado, não deve ser confundido com o comportamento não especificado . Comportamento não especificado é o comportamento correto do programa correto, mas que, com a permissão do Padrão, depende do compilador. E o compilador não é necessário para documentá-lo. Por exemplo, esta é a ordem na qual os argumentos de uma função são avaliados ou os detalhes de implementação de std::map .

Bem, aqui você pode se lembrar do comportamento definido pela implementação. De não especificado difere na disponibilidade de documentação. Exemplo: o compilador é livre para criar o tipo std::size_t qualquer tamanho, mas deve indicar qual.

Leia mais: um (russo) , dois (russo) , três (inglês)

As sutilezas da língua


ADL


Pesquisa dependente de argumento. Pesquisa dependente de argumento. Ele é a busca por Koenig - em homenagem a Andrew Koenig. Este é um conjunto de regras para resolver nomes de funções não qualificados (ou seja, nomes sem o operador :: :), além da resolução de nomes usual. Simplificando: o nome de uma função é pesquisado nos espaços de nomes relacionados aos seus argumentos (este é o espaço que contém o tipo do argumento, o próprio tipo, se for uma classe, todos os seus ancestrais etc.).

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

A função f encontrada no espaço para nome N apenas porque seu argumento pertence a esse espaço.

Até o trivial std::cout << "Hello World!\n" usa ADL , std::basic_stream::operator<< não std::basic_stream::operator<< sobrecarregado para const char* . Mas o primeiro argumento para esta declaração é std::basic_stream , e o compilador pesquisa e encontra uma sobrecarga adequada no std .

Alguns detalhes: A ADL não é aplicável se uma pesquisa regular encontrar uma declaração de um membro da classe ou uma declaração de função no bloco atual sem using , ou uma declaração de nem uma função nem um modelo de função. Ou se o nome da função estiver indicado entre parênteses (o exemplo acima não compila com (f)(s) ; você precisará escrever (N::f)(s); ).

Às vezes, a ADL obriga a usar nomes de funções totalmente qualificados onde parece desnecessário.

Por exemplo, esse código não compila
 namespace N1 { struct S {}; void foo(S) {}; } namespace N2 { void foo(N1::S) {}; void bar(N1::S s) { foo(s); } } 


Leia mais: um (inglês) , dois (inglês) , três (inglês)

CRTP


Curiosamente recorrente modelo padrão. Padrão recursivo estranho. A essência do modelo é a seguinte:

  • alguma classe herda da classe de modelo
  • a classe descendente é usada como um parâmetro de modelo de sua classe base

É mais fácil dar um exemplo:

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

O CRTP é um excelente exemplo de polimorfismo estático. A classe base fornece uma interface; as classes derivadas fornecem uma implementação. Mas, diferentemente do polimorfismo comum, não há sobrecarga para criar e usar uma tabela de funções virtuais.

Exemplo
 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(); } 

Quando usado corretamente, T sempre um descendente de Base , portanto, static_cast é suficiente para static_cast . Sim, neste caso, a classe base conhece a interface descendente.

Outra área comum de uso do CRTP é a extensão (ou restrição) da funcionalidade das classes herdadas (algo chamado mixin em alguns idiomas). Talvez os exemplos mais famosos:

  • struct Derived : singleton<Derived> { … }
  • struct Derived : private boost::noncopyable<Derived> { … }
  • struct Derived : std::enable_shared_from_this<Derived> { … }
  • struct Derived : counter<Derived> { … } - conta o número de objetos criados e / ou existentes

Desvantagens, ou melhor, momentos que requerem atenção:

  • Não há classe base comum, você não pode criar uma coleção de descendentes diferentes e acessá-los através de um ponteiro para o tipo base. Mas se você quiser, poderá herdar o Base do tipo polimórfico usual.
  • Há uma oportunidade adicional de tirar o pé do descuido:

    Exemplo
     template <typename T> struct Base {}; struct Derived1 : Base<Derived1> {}; struct Derived2 : Base<Derived1> {}; 

    Mas você pode adicionar proteção:

     private: Base() = default; friend T; 
  • Porque Como todos os métodos são não virtuais, os métodos do descendente ocultam os métodos da classe base com os mesmos nomes. Portanto, é melhor chamá-los de maneira diferente.
  • Em geral, os descendentes têm métodos públicos que não devem ser usados ​​em nenhum lugar, exceto na classe base. Isso não é bom, mas é corrigido através de um nível adicional de abstração (consulte FTSE ).


Leia mais: uma vez (russo) , dois (inglês)

CTAD


Dedução de Argumento do Modelo de Classe. Inferindo automaticamente o tipo do parâmetro do modelo de classe. Este é um novo recurso do C ++ 17. Anteriormente, apenas os tipos de variáveis ​​( auto ) e os parâmetros do modelo de função eram exibidos automaticamente, e é por isso que funções auxiliares como std::make_pair , std::make_tuple etc. std::make_tuple , na maioria das vezes, elas não são necessárias, porque o compilador capaz de exibir automaticamente os parâmetros dos modelos de classe:

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

O CTAD é uma nova oportunidade, precisa ser mais desenvolvido e desenvolvido (o C ++ 20 já promete melhorias). Enquanto isso, as restrições são as seguintes:

  • Inferência parcial de tipos de parâmetro não é suportada
     std::pair<double> p{1, 2}; //  std::tuple<> t{1, 2, 3}; //  
  • Aliases de modelo não suportados
     template <class T, class U> using MyPair = std::pair<T, U>; MyPair p{1, 2}; //  
  • Os construtores disponíveis apenas nas especializações de modelo não são suportados.
     template <class T> struct Wrapper {}; template <> struct Wrapper<int> { Wrapper(int) {}; }; Wrapper w{5}; //  
  • Modelos aninhados não são suportados
     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 
  • Obviamente, o CTAD não funcionará se o tipo do parâmetro do modelo não estiver relacionado aos argumentos do construtor.
     template <class T> struct Collection { Collection(std::size_t size) {}; }; Collection c{5}; //  

Em alguns casos, regras explícitas de inferência que devem ser declaradas no mesmo bloco que o modelo de classe ajudarão.

Exemplo
 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 


Leia mais: uma vez (russo) , dois (inglês)

EBO


Otimização da base vazia. Otimização de uma classe base vazia. Também chamado de EBCO (Empty Base Class Optimization).

Como você sabe, em C ++, o tamanho de um objeto de qualquer classe não pode ser zero. Caso contrário, toda a aritmética dos ponteiros será interrompida, porque em um endereço será possível marcar quantos objetos diferentes você desejar. Portanto, mesmo objetos de classes vazias (ou seja, classes sem um único campo não estático) têm um tamanho diferente de zero, que depende do compilador e do SO e geralmente é igual a 1.

Assim, a memória é desperdiçada em vão em todos os objetos de classes vazias. Mas não os objetos de seus descendentes, porque neste caso o Padrão explicitamente faz uma exceção. É permitido ao compilador não alocar memória para uma classe base vazia e, assim, salvar não apenas 1 byte da classe vazia, mas todos os 4 (dependendo da plataforma), pois também há alinhamento.

Exemplo
 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 

Mas como objetos diferentes do mesmo tipo não podem ser colocados no mesmo endereço, o EBO não funcionará se:

  • Uma classe vazia é encontrada duas vezes entre ancestrais
     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 
  • O primeiro campo não estático é um objeto da mesma classe vazia ou de seu descendente
     struct Empty {}; struct Foo : Empty { Empty e; int i; }; std::cout << sizeof(Empty) << std::endl; // 1 std::cout << sizeof(Foo) << std::endl; // 8 

Nos casos em que objetos de classes vazias são campos não estáticos, nenhuma otimização é fornecida (por enquanto, o atributo [[no_unique_address]] aparecerá em C ++ 20). Mas gastar 4 bytes (ou quanto o compilador precisa) para cada um desses campos é uma pena, então você pode "recolher" objetos de classes vazias com o primeiro campo não estático não vazio por conta própria.

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

Estranho, mas nesse caso, o tamanho do Foo é diferente para diferentes compiladores, para o MSVC 2019 é 8, para o GCC 8.3.0 é 4. Mas, em qualquer caso, aumentar o número de classes vazias não afeta o tamanho do Foo .

Leia mais: uma vez (inglês) , duas (inglês)

IIFE


Expressão de função invocada imediatamente. Expressão funcional chamada imediatamente. Em geral, esse é um idioma em JavaScript, de onde Jason Turner o emprestou junto com o nome. Na verdade, é apenas criar e chamar imediatamente um lambda:

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

Por que isso é necessário? Bem, por exemplo, como no código acima, para inicializar uma constante pelo resultado de um cálculo não trivial e não entupir o escopo com variáveis ​​e funções desnecessárias.

Leia mais: uma vez (inglês) , duas (inglês)

NVI


Interface não virtual. Interface não virtual. De acordo com esse idioma, uma interface de classe aberta não deve conter funções virtuais. Todas as funções virtuais são tornadas privadas (máximo protegido) e chamadas dentro de não virtuais abertas.

Exemplo
 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 { } }; 

Por que isso é necessário:

  • Cada função virtual aberta faz duas coisas: define a interface pública da classe e participa da redefinição de comportamento nas classes descendentes. O uso de NVI elimina essas funções com uma carga dupla: a interface é definida por algumas funções, mudança de comportamento por outras. Você pode alterar os dois independentemente um do outro.
  • (- -, . .), (. DRY ) — — . I.e. .

NVI – , (- ) (. FBC ).

: (.) , (.)

RVO NRVO


(Named) Return Value Optimization. () . copy elision – , . , ( copy elision – ).

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

RVO Foo bar , main ( bar ), f . RVO , bar f .

: main f . bar ( ), , .

NRVO RVO , , return , .

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

, NRVO , . , , , — 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.

: (.) , (.) , (.)

SFINAE


Substitution Failure Is Not An Error. — . SFINAE — — — ++. , , , . , :

  1. — (. ADL ).
  2. — , , , . .
  3. (viable functions), . — .

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 . .

: (.) , (.) , (.)

SBO, SOO, SSO


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 ( ). I.e. 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 .

: (.) , (.)

UDPATE


PyerK .

CV


const volatile. const , / , , UB . volatile , / (, - - ), . volatile volatile UB .

: (.) , (.) , (.)

LTO


Link Time Optimization. . , , . . . , : , . , .

: (.)

PCH


Precompiled Headers. . , . , .

: (.)

PGO


Profile-Guided Optimization. . , , . , .

: (.)

SEH/VEH


Structured/Vectored Exception Handling. MSVC . try-catch SEH : __try , __except , __finally , , , , - , . . VEH , .

: (.)

TMP


Template Meta-Programming. . — . C++ . . , TMP C++ , . . .

: (.)

VLA


Variable-Length Arrays. . I.e. , :
 void foo(int n) { int array[n]; } 

C++ . , . . C C99. C++ .

: (.)

PS


- - — . , , , C++. , , .

Source: https://habr.com/ru/post/pt470265/


All Articles