C ++缩写备忘单等。 第1部分:C ++

有一次,我在一个体面甚至知名的办公室接受了C ++开发人员的职位面试。 那时我已经有一些经验,当时我甚至被雇主称为领先开发人员。 但是当被问到我是否知道诸如DRY,KISS,YAGNI,NIH之类的东西时,我不得不反复回答“不”。

我当然惨败了。 但是随后,上面的缩写被谷歌搜索并被记住。 当我阅读主题文章和书籍,准备接受采访以及与同事交谈时,我学到了更多新事物,忘记了它们,再次谷歌搜索并理解了。 几个月前,我的一位同事在IIFE关于C ++的工作聊天中随意提及。 就像那个祖父在开玩笑一样,我差点从火炉上摔下来,再次进入Google。



那时,我决定(主要是为我自己)撰写一份备忘单,以供C ++开发人员了解有用的缩写。 这并不意味着它们仅适用于C ++,也不意味着它们是C ++的所有概念(您可以编写有关语言习语的书籍)。 不,这些只是我在工作和面试中实际遇到的概念,通常以缩写形式表示。 好吧,我错过了LIFO,FIFO,CRUD,OOP,GCC和MSVC等绝对琐碎的事情。

尽管如此,缩写还是相当不错的,所以我将备忘单分为两部分:C ++的强特性和更常见的特性。 在适当的时候,我将这些概念归为一组,否则我只是按字母顺序列出它们。 通常,它们的顺序没有多大意义。

基本的东西:
ODR
POD
POF
PIMPL
RAII
RTTI
STL
UB

语言的细微之处:
ADL
CRTP
CTAD
EBO
IIFE
NVI
RVO和NRVO
SFINAE
SBO,SOO,SSO

更新:
简历
LTO
PCH
PGO
SEH / VEH
TMP
VLA

基本的东西


ODR


一个定义规则。 一个定义的规则。 简化意味着:

  • 在一个翻译单元中,每个变量,函数,类等的定义不得超过一个。 有尽可能多的广告(没有给定基本类型的传输除外,除非定义,否则就不能声明),但不得超过一个定义。 如果不使用实体,则可能性较小。
  • 在整个程序中,使用的每个非内联函数和变量都必须具有唯一的定义。 使用的每个内联函数和变量在每个转换单元中必须有一个定义。
  • 某些实体(例如类,内联函数和变量,模板,枚举等)可以在程序中具有多个定义(但在翻译单元中不能超过一个)。 实际上,例如,当包含完全实现的类的同一标头连接到多个.cpp文件时,就会发生这种情况。 但是这些定义应该是一致的(我大大简化了,但本质是这样)。 否则将是UB

编译器将很容易在翻译单元中发现违反ODR的情况。 但是如果在程序规模上违反了规则,他将无能为力-仅因为编译器一次处理一个翻译单元。

链接器可以发现更多的违规行为,但是严格来说,他没有义务这样做(因为根据标准, UB在此处)并且可以丢失某些内容。 另外,在链接阶段搜索ODR违规的过程具有二次复杂性,并且C ++代码的汇编不是那么快。

因此,遵守此规则(尤其是在程序级别)的主要责任是开发人员本人。 是的-只有具有外部链接的实体才能在程序规模上违反ODR ; 内部的内容(即在匿名名称空间中定义的内容)不参与此狂欢。

阅读更多: 一(英语)两(英语)

豆荚


普通的旧数据。 简单的数据结构。 最简单的定义:您可以直接以二进制形式向C库发送/从C库接收这种结构。 或者,同一件事,请使用简单的memcpy正确复制。

从标准到标准,完整定义已详细更改。 最新的C ++ 17 POD当前定义了

  • 标量类型
  • 或一个类/结构/联合:
    -有一个琐碎的课
    -有配备标准装置的课程
    -不包含非POD非静态字段
  • 或这些类型的数组

普通班:

  • 至少有一个未删除:
    -默认构造函数
    -复制构造函数
    -移动构造函数
    -复制分配运算符
    -移动分配运算符
  • 复制和移动构造函数和赋值运算符的所有默认构造函数都是微不足道的(简化的-由编译器生成)或远程的
  • 有一个琐碎的非远程析构函数
  • 所有基本类型和类类型的所有字段都具有琐碎的析构函数
  • 没有虚拟方法(包括析构函数)
  • 没有虚拟基础类型

具有标准设备的类(标准布局类):

  • 没有虚拟方法
  • 没有虚拟基础类型
  • 没有非静态链接字段
  • 所有非静态字段都具有相同的访问修饰符(公共/受保护/私有)
  • 所有非静态字段和基类也是带有标准设备的类型
  • 该类本身及其所有祖先的所有非静态字段都在一个单独的类中声明(即,在类本身或一个祖先中)
  • 它不会两次继承相同的类型,即无法做到这一点:
     struct A {}; struct B : A {}; struct C : A{}; struct D : B, C {}; 
  • 第一个非静态字段的类型,或者,如果它是一个数组,则其元素的类型必须与任何基本类型都不相同(在这种情况下是由于强制性EBO

但是,在C ++ 20中,将不再存在POD类型的概念,仅保留普通类型和带有标准设备的类型。

阅读更多: 一名(俄语)两名(英语)三名(英语)

POF


普通的旧函数。 一个简单的C样式函数 ,仅在信号处理程序的上下文中在C ++ 14之前的标准中提及。 要求是:

  • 仅使用C和C ++通用的东西(例如,没有例外和try-catch
  • 不会直接或间接导致非POF函数,除了无原子块操作( std::atomic_initstd::atomic_fetch_add等)

本标准仅将这些功能(也具有C链接)( extern "C" )用作信号处理程序。 对其他功能的支持取决于编译器。

在C ++ 17中, POF的概念消失了,而不是出现信号安全评估。 在这种计算中被禁止:

  • 调用标准库的所有函数,除了原子,无锁
  • newdelete呼叫
  • 使用dynamic_cast
  • 调用thread_local实体
  • 任何例外的工作
  • 局部静态变量的初始化
  • 等待静态变量初始化完成

如果信号处理程序执行上述任一操作,则标准承诺UB

阅读更多: 时间(英语)

PIMPL


实施的指针。 指向实现的指针。 C ++中的经典习语,也称为d指针,不透明指针,编译防火墙。 它包含以下事实:某个类的所有私有方法,字段和其他实现细节都分配到一个单独的类中,而只有公共方法(即一个接口)和指向该新的单独类的实例的指针保留在原始类中。 例如:

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


为什么这是必要的,即优点:

  • 封装:通过头连接的类的用户只能获得他们需要的东西-公共接口。 如果实现细节发生变化,则不必重新编译客户端代码(请参阅ABI )。
  • 编译时间:由于公共头文件对实现一无所知,因此它不包含所需的许多头文件。 因此,减少了客户端代码中隐式连接的标头的数量。 还可以简化对重载的名称和解析的搜索,因为公共标题不包含私有成员(尽管它们是私有成员,但它们参与这些过程)。

价格,即劣势:

  • 访问公共方法时,至少要有一个指针取消引用,再加上一个函数调用。
  • 所需的内存类的大小会因指针的大小而增加。
  • 此内存的一部分(很可能更大)分配在堆上,这也对性能产生负面影响。
  • 逻辑恒定性很容易被破坏。 例如,这样的代码编译:

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

这些缺点中的一些缺点是可以消除的,但是价格进一步使代码复杂化,并引入了更多的抽象级别(请参阅FTSE )。

阅读更多: 一名(俄语)两名(俄语)三名(英语)

区域情报研究所


资源获取正在初始化。 捕获资源就是初始化。 这个成语的意思是,某种资源的保留在相应对象的整个生命周期中持续存在。 资源的捕获发生在对象的创建/初始化时,释放发生在相同对象的销毁/完成时。

奇怪的是(主要用于C ++程序员),这个习惯用法还用于其他语言,甚至那些带有垃圾收集器的语言。 在Java中,它是try-- ,在Python中是with语句,在C#中是using指令,在Go defer 。 但是, RAII尤其适合有机地使用具有绝对可预测对象寿命的C ++。

在C ++中,通常在构造函数中捕获资源,然后在析构函数中释放资源。 例如,智能指针以这种方式控制内存,文件流管理文件,互斥锁锁定互斥锁。 这样做的好处是,无论块如何退出(范围)-通过任何出口点正常运行还是抛出异常-在此块中创建的资源控制对象都将被销毁,并且资源将被释放。 即 除了将RAII封装在C ++中之外,它还有助于确保异常意义上的安全性。

局限,没有局限。 C ++中的析构函数不返回值,并且绝对不应引发异常。 因此,如果资源的释放伴随一个或另一个,则有必要在控制对象的析构函数中实现其他逻辑。

阅读更多: 一次(俄语)两次(英语)

RTTI


运行时类型信息。 在运行时进行类型识别。 这是一种用于在运行时获取有关对象或表达式类型的信息的机制。 它以其他语言存在,但在C ++中,它用于:

  • dynamic_cast
  • typeidtype_info
  • 捕获异常

一个重要的限制: RTTI使用虚拟函数表,因此仅适用于多态类型(虚拟析构函数就足够了)。 一个重要的解释: dynamic_casttypeid并不总是使用RTTI ,因此适用于非多态类型。 例如,要动态地将到后代的链接转换为到祖先的链接, 不需要RTTI ;所有信息在编译时都可用。

RTTI尽管不是免费的,但不是免费的,但它会对所消耗的内存的性能和大小产生负面影响(因此,经常建议不要使用dynamic_cast因为它的速度较慢)。 因此,通常,编译器允许您禁用RTTI 。 GCC和MSVC承诺这不会影响捕获异常的正确性。

阅读更多: 一次(俄语)两次(英语)

STL


标准模板库。 标准模板库。 C ++标准库的一部分,提供通用容器,迭代器,算法和帮助函数。

尽管它的名称众所周知,但STL从未在标准中如此称呼。 从标准的各个部分,可以明确将STL归因于Containers库,Iterators库,Algorithm库以及部分General Utility库。

在职位描述中,您经常可以找到2个独立的需求-C ++知识和对STL的熟悉。 我从不理解这一点,因为自1998年第一版标准以来, STL就已成为语言不可分割的一部分。

阅读更多: 一次(俄语)两次(英语)

UB


未定义的行为。 未定义的行为。 在没有标准要求的错误情况下,这种行为。 这些标准中有许多在UB中被明确列出。 这些包括,例如:

  • 违反数组或STL容器的边界
  • 使用未初始化的变量
  • 解引用空指针
  • 有符号整数溢出

UB的结果取决于连续的所有内容-既取决于编译器版本,又取决于火星的天气。 而且,此结果可能是任何事情:编译错误,正确执行和崩溃。 不确定的行为是邪恶的,有必要摆脱它。

另一方面,不应将未定义的行为与未指定的行为混淆。 未指定的行为是正确程序的正确行为,但在标准允许的情况下,取决于编译器。 并且不需要编译器对其进行记录。 例如,这是评估函数参数的顺序或std::map的实现细节。

好了,在这里您可以回顾实现定义的行为。 与未指定的文档可用性不同。 示例:编译器可以自由地将std::size_t类型设置std::size_t任意大小,但必须指出哪种类型。

阅读更多: 一名(俄语)两名(俄语)三名(英语)

舌头的微妙之处


ADL


基于参数的查找。 依赖参数的搜索。 他是对Koenig的搜寻-以此纪念Andrew Koenig。 除了通常的名称解析之外,这是一组用于解析不合格函数名称(即,不带::运算符的名称)的规则。 简而言之:在与函数自变量相关的名称空间中查找函数的名称(这是一个空间,其中包含参数的类型,类型本身(如果是类,则包括其祖先等)。)

最简单的例子
 #include <iostream> namespace N { struct S {}; void f(S) { std::cout << "f(S)" << std::endl; }; } int main() { N::S s; f(s); } 

仅在名称空间N找到函数f ,因为函数的参数属于该空间。

甚至普通的std::cout << "Hello World!\n"使用ADLstd::basic_stream::operator<< const char*不会重载std::basic_stream::operator<< 。 但是此语句的第一个参数是std::basic_stream ,编译器在std搜索并找到合适的重载。

一些细节:如果常规搜索在不使用的情况下在当前块中找到了类成员的声明或函数声明,或者不使用函数或函数模板的声明,则ADL不适用。 或者,如果函数名称在括号中指示(上面的示例未使用(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); } } 


阅读更多: 一种(英语)两种(英语)三种(英语)

CRTP


奇怪地重复的模板模式。 奇怪的递归模式。 模板的实质如下:

  • 一些类从模板类继承
  • 后代类用作其基类的模板参数

举个例子比较容易:

 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足以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> { … } -计算已创建和/或现有对象的数量

缺点,或者是需要引起注意的时刻:

  • 没有通用的基类,您无法创建不同后代的集合,也无法通过指向基类型的指针访问它们。 但是,如果需要,您可以从通常的多态类型继承Base。
  • 还有一个机会可以使您的脚摆脱粗心大意:

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

    但是您可以添加保护:

     private: Base() = default; friend T; 
  • 因为 由于所有方法都是非虚拟的,因此后代的方法将隐藏具有相同名称的基类的方法。 因此,最好以不同的方式命名它们。
  • 通常,后代具有公共方法,除了基类外,不应在其他任何地方使用。 这不是很好,但是可以通过附加的抽象级别进行纠正(请参阅FTSE )。


阅读更多: 一次(俄语)两次(英语)

CTAD


类模板参数推导。 自动推断类模板参数的类型。 这是C ++ 17的一项新功能。 以前,仅自动显示变量类型( auto )和函数模板参数,这就是为什么出现诸如std::make_pairstd::make_tuple等辅助函数的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是一个新的机会,它仍然需要发展和发展(C ++ 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 
  • 显然,如果模板参数的类型与构造函数参数不相关,则CTAD将不起作用。
     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 


阅读更多: 一次(俄语)两次(英语)

埃博


空基础优化。 空基类的优化。 也称为空基类优化(EBCO)。

如您所知,在C ++中,任何类的对象的大小不能为零。 否则,所有指针的运算都将中断,因为可以在一个地址上标记任意数量的不同对象。 因此,即使是空类的对象(即没有单个非静态字段的类)也具有一些非零的大小,这取决于编译器和OS,通常等于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 

如果空类的对象是非静态字段,则不会提供任何优化(目前,属性[[no_unique_address]]将出现在C ++ 20中)。 但是为每个这样的字段花费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的大小。

阅读更多: 一次(英语)两次(英语)

国际教育展


立即调用的函数表达式。 功能表达式立即调用。 通常,这是JavaScript中的一个惯用法,Jason Turner从中借用了它和名称。 实际上,它只是在创建并立即调用一个lambda:

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

为什么这是必要的? 好吧,例如,如上面的代码中所示,是为了通过非平凡的计算结果初始化常量,而不用不必要的变量和函数阻塞作用域。

阅读更多: 一次(英语)两次(英语)

NVI


非虚拟接口。 非虚拟接口。 根据这个习惯用法,开放类接口不应包含虚函数。 所有虚拟函数都设为私有(最大保护)并在非虚拟内部调用。

例子
 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的使用消除了此类功能,带来了双重负担:接口由某些功能定义,行为由其他功能定义。 您可以彼此独立地更改它们。
  • 如果对实现虚拟功能的所有选项(检查前和检查后,互斥量捕获等)有一些一般性要求,那么将它们收集在一个位置(请参见DRY)-在基类中-并防止继承者覆盖是非常方便的这种行为。 原来是模式Template方法的一种特殊情况。

使用NVI的代价是代码膨胀,性能可能下降(由于调用了一个额外的方法)以及对脆弱的基类问题的敏感性增加(请参阅FBC)。

阅读更多:一次(英语)两次(英语)

RVO和NRVO


(命名)返回值优化。优化(命名)返回值。这是标准允许的复制省略的一种特殊情况-编译器可以忽略不必要的临时对象副本,即使它们的构造函数和析构函数具有明显的副作用。当函数按值返回对象时,这样的优化是允许的(其他两种允许的复制省略情况是引发和捕获异常)。

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

如果没有RVO,将在此处创建Foo函数中的一个临时对象bar,然后通过复制构造函数从中创建函数中的另一个临时对象main(以获取结果bar),然后才创建f该对象并将第二个临时对象的值分配给它。RVO摆脱了所有这些复制和分配,并且该函数bar直接创建f

发生这种情况:函数main在其堆栈框架中为对象分配空间f。函数bar(已在其框架中工作)可以访问在前一框架中分配的该内存,并在那里创建所需的对象。

NRVORVO进行了相同的优化,但是不是在expression中return创建对象时,而是在返回先前在函数中创建的对象时进行的优化

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

尽管差异看似很小,但NRVO实施起来却困难得多,因此在许多情况下它不起作用。例如,如果一个函数返回一个全局对象或其参数之一,或者一个函数有多个退出点并且通过它们返回了不同的对象,则NRVO将不适用。

NRVO在这里不起作用
 Foo bar(bool condition) { if (condition) { Foo f1; return f1; } Foo f2; return f2; } 

几乎所有编译器都长期支持RVONRVO的支持程度可能因编译器和版本而异。

RVONRVO只是优化。并且虽然没有调用复制构造函数和赋值运算符,但是它们应该在对象的类中。在C ++ 17中,规则有所变化:现在RVO不被视为复制省略,它已成为强制性的,并且不需要相应的构造函数和赋值运算符。

注意:(N)恒定RVO是一个比较滑的话题。直到C ++ 14包括在内,对此一无所知,C ++ 17 在此类表达式中要求使用RVO,而即将发布的C ++ 20禁止使用。

关于位移的语义的几句话。首先,(N)RVO仍然更有效,因为 无需调用move构造函数和析构函数。其次,如果不是result从同一个函数返回std::move(result)而是保证NRVO不起作用。释义标准:RVO适用于prvalue,NRVO适用于lvalue,a std::move(result)为xvalue。

阅读更多:一种(英语)两种(英语)三种(英语)

FINAE


替换失败不是错误。替换失败不是错误。 SFINAE是C ++中模板(函数和类)的实例化过程的功能。底线是,如果无法实例化某个模板,则在存在其他选项的情况下,这不会被视为错误。例如,用于选择最合适的函数重载的简化算法如下所示:

  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 } 

static if在某些情况下,C ++ 17中出现的内容可能会取代SFINAE,而C ++ 20中预期的概念几乎将使它变得不必要。让我们看看。

阅读更多:一名(俄语)两名(英语)三名(英语)

SBO,SOO,SSO


小缓冲区/对象/字符串优化。优化小缓冲区/对象/行。有时,SSO在“小型优化”的含义中使用,但很少使用,因此我们假定SSO与字符串有关。SBOSOO只是同义词,而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

阅读更多:时间(英语)两个

UDPATE


感谢PyerK提供此附加的缩写列表。

简历


限定符,例如const和volatile。 const意味着该对象/变量不能被修改,尝试这样做将导致在编译时或运行时UB导致错误volatile意味着对象/变量可以更改,而与程序的操作无关(例如,某些微控制器将某些内容填充到内存中),并且编译器不应优化对其的访问。volatile不通过volatile链接或指针访问对象也会导致UB

阅读更多:一(俄语)两(英语)三(俄语)

LTO


链接时间优化。链接优化。顾名思义,这种优化发生在链接期间,即在编译之后。链接器可以执行编译器不敢执行的操作:内联一些函数,丢弃未使用的代码和数据。当然,增加链接时间。

阅读更多:时间(英语)

PCH


预编译的标题。预编译的头文件。经常使用但很少修改的头文件会被编译一次并以内部编译器格式保存。因此,重新组装项目将花费更少的时间,有时甚至更少。

了解更多:时间(前)

Pgo


配置文件引导的优化。根据分析结果进行优化。这是一种程序优化方法,但不是通过静态代码分析,而是通过测试程序启动和收集实际统计信息。例如,可以优化以这种方式分支和调用虚拟函数。

了解更多:时间(前)

Seh / veh


结构化/矢量化异常处理。这是用于异常和错误处理的MSVC扩展。不同于标准的try-catch SEH使用其自己的关键字:__try__except__finally,抓住和处理不明确抛出的异常,这样的事情作为访问一个无效的内存堆栈溢出由于无限递归调用纯虚函数,等等VEH它不会显式捕获每个错误,但是会创建一个全局的错误处理程序链。

阅读更多:时间(英语)

Tmp


模板元编程。模板元编程。元编程是指一个程序由于其工作而创建另一个程序。C ++中的模板实现了这种元编程。模板编译器生成所需数量的类或函数。众所周知,C ++ 中的TMP是图灵完备的,即可以在其上实现任何功能。

了解更多:时间(前)

弗拉


可变长度数组。可变长度的数组。 在编译时长度未知的数组:
 void foo(int n) { int array[n]; } 

C ++标准不允许这样做。这有点奇怪,因为它们自C99标准以来就存在于纯C语言中。并且被某些C ++编译器支持作为扩展。

了解更多:时间(前)

聚苯乙烯


如果我错过了某件事或在某个地方弄错了-在评论中写。请记住,这里只列出了与C ++直接相关的缩写。对于其他人(但同样有用),将有一个单独的帖子。

第二部分

Source: https://habr.com/ru/post/zh-CN470265/


All Articles