C++模板元编程深度解析:探索编译时计算的神奇之旅

Posted 泡沫o0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++模板元编程深度解析:探索编译时计算的神奇之旅相关的知识,希望对你有一定的参考价值。

C++模板元编程深度解析:探索编译时计算的神奇之旅

引言

C++模板元编程的概念与作用

C++ 模板元编程(Template Metaprogramming,简称 TMP)是一种在编译期间运行的编程技术,它允许使用 C++ 模板系统进行计算和操作。在模板元编程中,模板是用来表示计算和数据结构的主要工具,而编译器则是执行这些计算的引擎。其主要目的是生成高效、可复用的代码,实现编译期的代码优化和泛型编程。

模板元编程在现代C++编程中的应用

模板元编程在现代 C++ 编程中的应用广泛,例如:

  1. 编译期计算:利用模板元编程在编译期间计算常量表达式,减少运行时的开销。例如,使用递归模板计算斐波那契数列、阶乘等。
  2. 泛型编程:实现通用的、可重用的代码,降低代码重复度。例如,C++ 标准库中的 STL(Standard Template Library)容器和算法就使用了模板元编程技术。
  3. 策略模式:通过模板参数将算法和数据结构解耦,使得算法和数据结构可以独立地进行扩展和优化。
  4. 静态分派:利用模板特化和偏特化实现静态多态,避免运行时的虚函数开销,提高代码执行效率。
  5. 类型推导与类型萃取:模板元编程可以实现编译期的类型检查和类型操作,例如 std::enable_ifstd::is_same 等类型萃取和类型操作工具。

模板元编程基础

类型萃取(Type Traits)

类型萃取(Type Traits)是 C++ 模板元编程中一种用于获取和操作类型信息的技术。C++ 标准库 <type_traits> 头文件提供了一系列的类型萃取工具,用于查询和操作类型信息,例如 std::is_same(判断两个类型是否相同)、std::remove_reference(移除引用)、std::enable_if(根据条件启用函数重载)等。

例如,使用 std::is_same 判断两个类型是否相同:

#include <type_traits>
#include <iostream>

int main() 
    std::cout << std::boolalpha
              << std::is_same<int, int>::value << '\\n' // 输出 true
              << std::is_same<int, float>::value;      // 输出 false
    return 0;


编译时条件(静态if)

编译时条件(静态if)是 C++17 引入的一项新特性,允许在编译期根据条件选择性地执行代码。它通过 if constexpr 语法实现,其条件必须是一个编译期常量表达式。在编译期,if constexpr 会根据条件选择要编译的分支,未选中的分支不会被编译。

例如,使用 if constexpr 实现编译期阶乘计算:

#include <iostream>

template<int N>
constexpr int factorial() 
    if constexpr (N == 0) 
        return 1;
     else 
        return N * factorial<N - 1>();
    


int main() 
    constexpr int result = factorial<5>();
    std::cout << result; // 输出 120
    return 0;


模板元编程中的递归与终止条件

递归是模板元编程中实现编译期计算的关键技术。在模板元编程中,递归通常通过模板的特化和偏特化来实现。为了避免无限递归,需要设置适当的终止条件。

以斐波那契数列为例,递归模板和终止条件的实现如下:

//当 N 分别为 0 和 1 时,递归终止,并返回相应的值。
#include <iostream>

// 递归模板
template<int N>
struct Fibonacci 
    static const int value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
;

// 终止条件 1
template<>
struct Fibonacci<0> 
    static const int value = 0;
;

// 终止条件 2
template<>
struct Fibonacci<1> 
    static const int value = 1;
;

int main() 
    std::cout << Fibonacci<10>::value; // 输出 55
    return 0;


模板元编程技巧与工具

值计算(Value Computation)

值计算是模板元编程的一种常见技巧,用于在编译期计算常量表达式的值。常见的值计算技术包括使用 constexpr 函数和模板递归。使用这种技巧可以在编译期计算各种数学函数、算法和序列。

例如,使用 constexpr 函数计算阶乘:

template<int N>
constexpr int factorial() 
    return (N == 0) ? 1 : (N * factorial<N - 1>());


类型计算(Type Computation)

类型计算是模板元编程的另一种关键技巧,用于根据输入类型计算输出类型。类型计算可以用于实现类型变换、类型过滤等功能。类型萃取(Type Traits)是一种常见的类型计算技术,C++ 标准库 <type_traits> 头文件提供了一系列实用的类型萃取工具。

例如,使用 std::conditional 计算条件类型:

#include <type_traits>

template<bool B, typename T, typename F>
using Conditional = typename std::conditional<B, T, F>::type;

// Usage
using T = Conditional<true, int, float>;  // T 是 int 类型
using F = Conditional<false, int, float>; // F 是 float 类型

编译时数据结构(Compile-Time Data Structures)

编译时数据结构是一种在编译期间存储和操作数据的技术。与运行时数据结构(如 std::vectorstd::list 等)相比,编译时数据结构可以在编译期提前完成一部分计算,从而在运行时节省资源。

C++11 引入的 std::integer_sequence 是一个编译时数据结构的例子,它表示一个整数序列。std::integer_sequence 可以用于生成编译期整数序列,并将其用于元编程算法。C++14 引入的 std::index_sequencestd::integer_sequence 的一个特化,用于表示类型索引序列。

#include <utility>
#include <iostream>

template<typename T, T... Ints>
void print_integer_sequence(std::integer_sequence<T, Ints...>) 
    ( (std::cout << Ints << ' '), ...);


int main() 
    print_integer_sequence(std::make_integer_sequence<int, 5>); // 输出 0 1 2 3 4
    return 0;


编译时数据结构还可以用于实现编译期容器(如编译时数组、编译时链表等),并在编译期完成各种操作,例如查找、排序和过滤等。

C++11/14/17模板元编程新特性

类型推导(Type Deduction)

C++11 引入了自动类型推导(auto 关键字),允许编译器根据表达式的类型推导变量的类型。此外,C++11 还引入了尾返回类型推导,用于函数返回值的类型推导。C++14 进一步引入了返回类型推导,使得编译器可以自动推导普通函数的返回类型。这些特性在模板元编程中很有用,可以简化类型表达式和减少错误。

// C++11 尾返回类型推导
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) 
    return t + u;


// C++14 返回类型推导
template<typename T, typename U>
auto add(T t, U u) 
    return t + u;


constexpr函数

C++11 引入了 constexpr 关键字,允许在编译期计算常量表达式。constexpr 函数是一种编译期函数,可以用于编译期计算。C++14 进一步放宽了 constexpr 函数的限制,允许包含更多类型的语句,使得编写编译期函数更加灵活。

// C++11
constexpr int square(int x) 
    return x * x;


// C++14
constexpr int gcd(int a, int b) 
    while (b != 0) 
        int r = a % b;
        a = b;
        b = r;
    
    return a;


变量模板(Variable Template)

C++14 引入了变量模板,允许定义模板变量。变量模板可以用于在编译期定义和计算常量值。在模板元编程中,变量模板可以用于简化编译期常量的表示和计算。

template<typename T>
constexpr T pi = T(3.1415926535897932385);

// Usage
constexpr double pi_double = pi<double>;
constexpr float pi_float = pi<float>;

折叠表达式(Fold Expressions)

C++17 引入了折叠表达式,允许在编译期对参数包(Parameter Pack)进行折叠操作。折叠表达式可以简化模板元编程中可变参数模板的编写,提高编程效率。

// C++17 折叠表达式
template<typename... T>
auto sum(T... args) 
    return (... + args);


// Usage
int result = sum(1, 2, 3, 4, 5); // result = 15

折叠表达式可以与一元或二元操作符一起使用,例如 +*&&|| 等。折叠表达式的语法包括:(init op ...)(... op init)(... op)(op ...)

其中,op 是操作符,init 是可选的初始值。当使用无初始值的形式时,编译器将自动选择恰当的初始值。

// 示例:计算参数包中所有参数的乘积
template<typename... T>
auto product(T... args) 
    return (... * args);


// Usage
int prod_result = product(2, 3, 4); // prod_result = 24

// 示例:检查参数包中所有参数是否都是 true
template<typename... Bool>
constexpr bool all_true(Bool... bools) 
    return (... && bools);


// Usage
constexpr bool all_true_result = all_true(true, true, false, true); // all_true_result = false

// 示例:检查参数包中是否有任意一个参数为 true
template<typename... Bool>
constexpr bool any_true(Bool... bools) 
    return (... || bools);


// Usage
constexpr bool any_true_result = any_true(false, true, false, false); // any_true_result = true

折叠表达式使得在模板元编程中处理可变参数模板更加简单和直观,提高了编程效率和代码可读性。

decltype 类型推导

C++11 引入了 decltype 关键字,允许编译器根据表达式推导出类型。decltype 在模板元编程中非常有用,因为它允许我们根据一定的操作推导出结果类型。这可以用来构建更加灵活和通用的模板元编程代码。

template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) 
    return t + u;


constexpr if 语句

C++17 引入了 constexpr if 语句,它允许在编译期根据条件来选择执行哪个分支。在模板元编程中,constexpr if 语句可以用来在编译期间选择不同的实现。

template<typename T>
constexpr auto get_size(const T& container) 
    if constexpr (std::is_same_v<T, std::list<typename T::value_type>>) 
        return container.size(); // Use 'size()' member function for std::list
     else 
        return std::distance(container.begin(), container.end()); // Use 'begin()' and 'end()' member functions for other containers
    


以下是一个示例代码,展示了如何使用constexpr if来实现模板特化:

template <typename T>
struct MyType 
  void doSomething() 
    if constexpr (std::is_integral_v<T>) 
      // 特化实现,当T是整数类型时执行
      std::cout << "MyType<" << typeid(T).name() << "> is an integer type.\\n";
     else 
      // 通用实现,当T不是整数类型时执行
      std::cout << "MyType<" << typeid(T).name() << "> is not an integer type.\\n";
    
  
;

int main() 
  MyType<int> intType;
  intType.doSomething();  // 输出:MyType<i> is an integer type.

  MyType<double> doubleType;
  doubleType.doSomething();  // 输出:MyType<d> is not an integer type.

  return 0;


在这个示例中,我们定义了一个模板类MyType,它接受一个类型参数T。在doSomething()函数中,我们使用constexpr if来检查T是否是整数类型。如果是,就执行特化实现,否则执行通用实现。这样,我们就可以根据T的类型选择性地编译不同的代码,从而实现模板特化。

别名模板(Alias Templates)

C++11 引入了别名模板,它允许我们为模板类型定义别名。在模板元编程中,这可以简化复杂类型的表示,提高代码可读性。

template<typename T>
using MyContainer = std::vector<T, MyAllocator<T>>;

// 使用 MyContainer
MyContainer<int> int_container;

static_assert

C++11 引入了 static_assert 关键字,它允许我们在编译期进行断言检查。这在模板元编程中非常有用,因为我们可以用它来检查模板参数是否满足某些约束。

template<typename T>
class MyClass 
    static_assert(std::is_integral<T>::value, "MyClass requires an integral type.");
    // ...
;

C++14 中的泛型 lambda 表达式

C++14 引入了泛型 lambda 表达式,允许在编译期生成不同类型的 lambda 表达式。这可以用来构建更加灵活的模板元编程代码。

auto add = [](auto t, auto u) 
    return t + u;
;

int int_result = add(1, 2);
float float_result = add(1.0f, 2.0f);

C++20 中的模板参数推断(Concepts)

C++20 引入了模板参数推断(Concepts),它允许在编译期约束模板参数的类型,提高了模板编程的可读性和安全性。Concepts 可以替换部分旧有的 std::enable_if 技术来约束模板参数。

#include <concepts>

template <std::integral T>
T my_sum(T a, T b) 
    return a + b;


int main() 
    int result = my_sum(1, 2); // OK
    // double error_result = my_sum(1.0, 2.0); // Error: Doesn't meet the concept constraint


C++17 中的结构化绑定

C++17 引入了结构化绑定,允许我们更简洁地解包容器或者元组等数据结构。在模板元编程中,结构化绑定可以简化元组操作,提高代码可读性。

#include <tuple>

template<typename... Args>
auto get_tuple(Args&&... args) 
    return std::make_tuple(std::forward<Args>(args)...);


int main() 
    auto [a, b, c] = get_tuple(1, 2, 3);
    // a = 1, b = 2, c = 3


C++20 中的 consteval 函数

C++20 引入了 consteval 关键字,用于标记必须在编译期计算结果的函数。这对于模板元编程中需要在编译期执行的计算非常有用。

consteval int pow2(int n) 
    return (n <= 0) ? 1 : 2 * pow2(n - 1);


int main() 
    constexpr int result = pow2(5); // result = 32, computed at compile-time


C++14 中的返回类型后置(Function Return Type Deduction)

C++14 引入了函数返回类型后置,它允许编译器根据函数体推导出函数的返回类型。这在模板元编程中非常有用,因为它允许我们简化模板函数的定义。

template <typename T, typename U>
auto add(T a, U b)  // 无需使用 -> decltype(a + b)
    return a + b;


C++17 中的 constexpr std::pairstd::tuple

C++17 引入了对 std::pairstd::tupleconstexpr 支持,这使得我们能够在编译时使用这些容器进行计算。

#include <tuple>

constexpr std::tuple<int, float> get_tuple() 
    return std::make_tuple(1, 1.5f);


int main() 
    constexpr auto [a, b] = get_tuple();
    // a = 1, b = 1.5, computed at compile-time


C++17 中的 inline 变量

C++17 引入了 inline 变量,它允许在头文件中定义全局变量,而无需担心多重定义的问题。这在编写通用模板库时非常有用。

// my_header.hpp
#pragma once

template <typename T>
inline T global_variable = T;

C++20 中的 using enum

C++20 引入了 using enum,它允许我们将枚举成员导入到当前作用域。这对于编写通用的元编程代码非常有帮助,因为我们可以在不同的作用域中更方便地使用枚举类型。

enum class Color 
    Red,
    Green,
    Blue
;

void use_color() 
    using enum Color;

    Color c

深度探索C++对象模型

深度探索C++对象模型

  • 什么是C++对象模型:
    • 语言中直接支持面向对象程序设计的部分.
    • 对于各个支持的底层实现机制.
  • 抽象性与实际性之间找出平衡点, 需要知识, 经验以及许多思考.

导读

  • 这本书是C++第一套编译器cfront的设计者所写.
  • 了解C++对象模型, 有助于在语言本身以及面向对象观念两方面层次提升.
    • explicit(明确出现于C++程序代码).
    • implicit(隐藏于程序代码背后).

关于对象

  • 每个非内联(non-inline)成员函数只会诞生一个函数实例. 而内联函数会在每个使用者身上产生一个函数实例.
  • C++在布局以及存取时间上的额外负担主要由虚(virtual)引起的:
    • 虚函数机制(virtual function)用于支持一个有效率的运行期绑定(runtime binding).
    • 虚基类(virtual base class) 用以实现多次出现在继承体系中的基类, 有一个单一而被共享的实例.
    • 额外负担, 派生类转换.
  • 在C++中, 有两种类数据成员: 静态(static) 和非静态(non-static).
    • 三种类成员函数: 静态(static), 非静态(non-static)和虚函数(virtual).
  • 每个数据成员或成员函数都有自己的一个slot(元素, 位置, 槽) --- 针对vtbl(虚表)而言.
  • 成员函数表(member function table)成为了支持虚函数(virtual function)的一个有效方案.

C++对象模型

  • 非静态数据成员被置于每一个类对象中, 静态数据成员被存放在个别的类对象之外.
    • 静态和非静态函数也被存放在个别的类对象之外.
    • 虚函数利用虚表(vbtl)和虚表指针(vptr)设置.
      • 每个类产生一堆指向虚函数的指针, 并放到表格之中.
      • 每个类对象被安插一个指针, 指向相关的virtual table(虚表).
        • vptr的设定与重置都由每一个类的构造, 析构和copy赋值运算符自动完成.
      • 每个类的type_info(类型信息)的对象也由虚表(virtual table)指出, 通常放在表格的第一个槽(slot).
        技术图片
  • 在虚拟继承的情况下, 基类不管在继承串链中被派生多少次, 永远只会存在一个实例.
  • class不仅是一个关键字, 它还会引入它所支持的封装和继承的哲学.
  • 某种意义上, 在C++中struct和class这两个关键字是可以互换的.
  • 基类和派生类的数据成员的布局没有谁先谁后的强制规定, 但使用初始化列表时, 必须保持成员变量顺序的一致性.
  • 组合而非继承, 才是把C和C++结合在一起的唯一可行方法.

对象的差异性

  • 三种程序设计范式:
    • 程序模型.
    • 抽象数据类型(基于对象).
    • 面向对象模型.
    • 应该还有一个模板编程(范式模型).
  • 只有通过指针或引用的间接处理基类对象, 才支持面向对象程序设计所需的多态性质.
  • C++中, 多态只存在与public 类体系中, nonpublic的派生行为和void*的指针的多态性, 必须由程序员来显式管理.
    • 隐式转换操作: 把一个派生类的指针转换为一个指向public基类类型的指针.
    • 由虚函数(virtual function)机制: ps->rotate();.
    • 由dynamic_cast和typeid运算符转换: dynamic_cast<base_class *> (derived_class *);.
  • 多态的主要用途是经由一个共同的接口来影响类型的封装, 这个接口一般定义在一个抽象的基类中.
  • 一个指针, 不管它指向那种数据类型, 其本身所需内存大小是固定的, 与计算机的位数一致.
  • 指针类型会教导编译器如何解释某个特定地址中的内存内容及其大小.
  • void*指针能够持有一个地址, 但不能通过 它来操作所指对象, 因为不知道其覆盖怎样的地址空间.
  • 派生类不会新添加虚表指针(vptr, 继续使用基类的指针), 只是覆盖的地址会有所不同.
  • 类型信息的封装并不是维护于指针之中, 而是维护于链接(link)之中, 此链接存在于对象的虚表指针(vptr), 和vptr所指的虚表(virtual table)之间.
  • 编译器必须确保每个对象有一个或一个以上的vptr, 这些vptr的内容不会被基类对象初始化或改变.
  • 一个指针或引用之所以支持多态, 是因为它们并不引发内存中任何与内存相关的内存委托操作, 会改变的只有他们所指内存的"大小和内容解释方式"而已.
  • 将派生类直接用于初始化基类对象时, 派生类对象会被切割以塞入较小的基类类型内存中.
  • C++通过指针(pointer)和引用(reference)来支持多态.

构造函数语义

  • 默认构造函数的构造操作:
    • 会插入一些构造函数的代码.
  • 编译器为未声明任何构造的类, 编译器会为他们合成一个默认的构造函数.
  • 被合成出来的构造函数只满足编译器的需要.
    • 合成的默认构造函数中, 只有基类派生对象成员类对象会被初始化.
    • 所有其他非静态数据成员(如整数, 整数指针, 整数数组等)都不会被初始化.
  • copy 构造函数的构造操作:
    • 默认成员初始化列表, 类似于深拷贝(bitwise copy).
    • 默认构造函数和默认copy构造函数在必要时才由编译器产生出来.
  • 一个类对象可以通过两种方式复制得到, 一种是被初始化(copy constructor), 另一种是被指定(copy assignment operator).
  • 位逐次拷贝(bitwise copy semantics(语义)):
    • 会拷贝每一个位(bit).
  • 什么时候不要位逐次拷贝:
    • 当类内含一个成员对象, 该成员对象中声明了一个copy 构造函数.
    • 类继承的基类中存在一个构造函数.
    • 类声明了一个或多个虚函数.
    • 当类派生自一个继承串连, 其中有一个或多个虚基类时.
  • 当编译器导入一个虚表指针(vptr)到一个类对象中时, 该类就不展现逐次语义(bitwise semantics)了.

程序转换语义(Program Transformation Semantics)

  • 显示的初始化操作(Explicit Initialization):
    • 程序转换有两个阶段:
      • 重写一个定义, 其中的初始化操作会被剥离.
      • 类的copy 构造调用操作会被安插进去.
  • 编译器可能做NRV(Named Return Value)优化操作.
  • 以一个类对象作为另一个类对象的初值的情形, C++允许编译器有大量的自由发挥空间, 以提升程序效率.
  • 必须使用成员初始化列表(member initialization list):
    • 当初始化一个成员引用(reference member)时.
    • 当初始化一个常量成员(const member)时.
    • 当调用一个基类的构造函数, 而该基类拥有一组参数时.
    • 当调用一个成员类的构造函数, 其拥有一组参数时.
  • 编译器会一一操作初始化列表(initialization list), 以适当顺序在构造函数之内安插初始化操作, 在显式之前.
    • 初始化列表中的顺序是由类的成员声明顺序决定的, 不是由初始化列表中的排列顺序决定的.
    • 顺序混乱会造成意想不到的危险.
    • 初始化列表中的项目被放在显示声明代码(explicit user code)之前.

Data语义

  • 一个空类会被编译器安插一个char, 使这个类的两个对象得以在内存中配置独一无二的地址.
  • 空虚基类(Empty virtual base class)已经称为C++面向对象的一个特有术语.
    • 提供了一个虚拟接口, 没有任何数据, 空虚基类被认为是派生对象开头的一部分, 不花费任何派生类的额外空间.
  • 虚基类自读爱香只会在派生类中存在一份实例, 不管它在class继承体系中出现了多少次.
  • 非静态成员数据放置的是个别类对象感兴趣的数据, 静态成员数据放置的是整个类感兴趣的数据.
    • 静态成员变量被放到全局数据段中, 不会影响个别类对象的大小. 不管生成多少个对象, 静态数据成员永远只存在一份实例.
    • 编译器自动加上额外的数据成员, 用以支持某些语言特性.
    • 因为内存对齐(alignment), 边界调整的需要. --- 类对象可能比想象的大.

数据成员的布局

  • 成员变量的排列顺序因编译器而异, 编译器可以随意选一个放在第一个.
  • 在C++中, 在同一access section(private, protected, public等区段)中, 成员的排列只需符合较晚出现的成员变量在类对象中有较高的地址.
  • 静态成员并不需要通过类对象进行访问.
    • 一个静态数据成员的地址是一个指向其数据类型的指针, 并不是一个指向类成员的指针.
  • 对一个非静态数据成员进行存取操作, 编译器需要把类对象的起始地址加上数据成员的偏移位置(offset).
    • 每个非静态数据成员的偏移位置(offset)在编译时期即可知晓.
  • 具体继承(concrete inheritance)并不会增加空间和存取时间上的额外负担.
  • 在每一个类对象(class object)中带入一个vptr, 提供执行期的链接, 使每一个object(对象)能够找到对应的虚表(虚virtual table).
    • 在派生类和基类中, 可能重新设定vptr的值.
      在析构函数中, 可能抹消掉指向类相关虚表(virtual table)的vptr.
  • vptr放在类对象的前端(起始处), 会丧失对C语言的兼容性.
  • 多重继承的问题主要发生于派生类对象和其第二或后继的基类对象之间的转换.
  • 取一个非静态数据成员的地址, 将得到它在类中的偏移量(offset); 取一个绑定于真正类对象身上的数据成员的地址, 将会得到该成员在内存中的真正地址.

函数(Function)语义

  • 静态成员函数:
    • 不能直接存取非静态成员数据.
    • 也不能被声明为const函数.
  • 一般而言, 成员的名称前面会被加上类名称, 以形成独一无二的命名.
  • 静态成员函数没有this指针.
  • 在C++中, 多态(polymorphsim)表示以一个public base class(公有基类)的指针(或引用), 寻址一个派生类对象.
    • 多态机能主要扮演一个传送机制的角色, 可以在程序任何地方采用一组public derived类型.
    • 有了RTTI(runtime tyoe identification)就能够在执行期查询一个多态的指针或多态的引用.
  • 虚拟继承是C++中多重继承中特有的概念, 虚拟继承的一些总结.
  • 深入探索C++对象模型笔记汇总

  • 内联(inline)函数中的局部变量, 再加上有副作用的参数, 可能会导致大量临时性的对象产生.

构造, 析构, 拷贝语义

  • 继承体系中每一个类对象的析构函数都会被调用.
  • 构造函数可能内含大量的隐藏diamante, 因为编译器会扩充每一个constructor, 扩充成都视class 的继承体系而定.
    • 记录在成员初始化列表中的数据成员初始化操作会被放进构造函数本体, 并以成员变量声明顺序为顺序.
    • 如果有一个成员变量没有出现在初始化列表中, 它有一个默认的构造函数, 那么该默认的构造函数必须被调用.
    • 类对象的虚表指针(virtual table pointer)必须被设置初值, 指向适当的虚表(virtual table).
    • 基类的构造函数必须被调用, 以基类的声明顺序为顺序.
    • 虚基类构造函数必须被调用, 从左到右, 从最深到最浅.
  • 如果类没有定义析构函数, 只有在类内的成员对象(基类)拥有析构函数时, 编译器才会自动合成一个出来.

执行期语意

  • C++所有的全局对象都被繁殖在程序的数据段(data segment)中.
  • 运算符new一般由两个步骤完成:
    • 通过适当的new运算符函数实例, 配置所需的内存.
    • 将配置来的对象设立初值.
  • 临时对象的摧毁应该是对完整表达式(full-expression)求值过程中的最后一个步骤.
    • 完整表达式(full-expression)是表达式最外围的那个.
  • 编译器不能消除class类型的局部临时变量, 因为C++back-ends的限制.
    • 可以通过一些优化工具把临时对象放进寄存器.

站在对象模型的尖端

  • 模板template, 异常处理exception handing(EH), 运行时类型识别(runtime type identification, RTTI).
  • 每一个可执行文件中只需要一份模板的实例, 每个编译单位都会拥有一份实例.
  • 只有在成员函数被使用的时候, C++标准才要求他们被实例化.
    • 空间和时间效率的考虑.
    • 尚未实现的机能.
  • 所有与类型相关的检验, 如果牵涉到template参数, 都必须延迟到真正的实例化操作(instantiation)发生, 才得为之.
  • Template中的名称决议法:
    • 定义模板(template)的程序端和实例化模板(template)的程序的区别.
    • 定义模板(template)专注于一般的模板类.
    • 实例化模板(template)专注于特定的实例.
  • 如果一个虚函数被实例化, 其实例化点紧跟在其类的实例化点之后.
  • dynamic_cast运算符可以再执行期决定真正的类型.
  • typeud运算符传回一个const reference, 类型为type_info.
  • 虽然RTTI只适用于多态类(polymorphic classes), 事实上type_info对象也适用于内建类型, 以及非多态的使用者自定义类型.
  • 动态共享函数库, 共享内存.

以上是关于C++模板元编程深度解析:探索编译时计算的神奇之旅的主要内容,如果未能解决你的问题,请参考以下文章

提高性能及操作硬件的能力

文字的算术运算是在编译时还是运行时计算的?

由于编译时计算的值,如何避免“无法访问的语句”?

C++ 模板元编程的最佳介绍? [关闭]

元宇宙|科技破壁对话 我们距未来还有多远?

解读C++ constexpr关键字的特性