разработка на c/c++, неизбежный пользовательский тип класса (часть 7).

Оглавление

1. Спецификатор Noexcept и функция-член класса

        1.1 нет, кроме грамматики и применения

         1.2 noexcept и дизайн класса

        1.3 noexcept и указатели на функции-члены

        1.4 noexcept и вызовы функций-членов

        1.5 noexcept и виртуальные функции

        1.6 требования noexcept и константных выражений

2. Пустой класс

        2.1 Пустые классы не пусты

        2.2 Оптимизация пустого базового класса

        2.3 [[no_unique_address]] оптимизировать пустой класс

Три, явный спецификатор

        3.1 форма явного синтаксиса

        3.2 Конструктор преобразования

        3.3 явные ограничения использования

        3.4 явные и константные выражения

Четыре, спецификатор constexpr

        4.1 Спецификатор constexpr и const

        4.2 Требования к использованию constexpr

        4.3 Использование constexpr

5. Дополнение к исходному коду


1. Спецификатор Noexcept и функция-член класса

        Начиная с С++ 11, в стандартной библиотеке появился спецификатор noexcept, который предназначен для замены throw и используется для указания того, генерирует ли функция исключение.

        1.1 нет, кроме грамматики и применения

        Синтаксис спецификатора noexcept следующий:

/*与 noexcept(true) 相同*/
noexcept

/*如果 表达式 求值为 true,那么声明函数不会抛出任何异常。
*表达式 - 按语境转换为 bool 类型的常量表达式 */
noexcept(表达式)

/*与 noexcept(true) 相同(C++17 前的语义见动态异常说明) 
*(C++17 起)(C++17 中弃用)(C++20 中移除) */
throw() 

        Спецификация noexcept является частью типа функции и может появляться как часть любого декларатора функции. Каждая функция, указанная в параметре noexcept, либо не вызывает генерацию, либо может ее генерировать.

#include <iostream>
int f1() noexcept; 
int f2() noexcept(false) { return 0; }
int f3(int val) noexcept(__cplusplus > 201103L){ return val/2; }
// f4 是否声明为 noexcept 取决于 T() 是否会抛出异常
template <class T> int f4() noexcept(noexcept(T())) { return 0;}

        noexcept также можно использовать в качестве оператора, который проверяется во время компиляции, становится частью выражения и возвращает значение true, если выражение не генерирует никаких исключений. Оператор noexcept не оценивает выражения. Результат истинен, если набор потенциальных исключений выражения пуст (до C++17) и выражение не вызывает исключение (начиная с C++17), в противном случае результат ложен.

void noexcept_test(void)
{
    bool b1 = noexcept(f1());           //noexcept作为运算符使用
    std::cout << "b1 = " << b1 << "\n" ;// true 
    bool b2 = noexcept(f2());     
    std::cout << "b2 = " << b2 << "\n" ;// false
    bool b3 = noexcept(f3(10));   
    std::cout << "b3 = " << b3 << "\n" ;// true,c++11后,false,c++11
    bool b4 = noexcept(f4<int>());     
    std::cout << "b4 = " << b4 << "\n" ;// true
}

        При добавлении к функции оператора noexcept(false), то есть noexcept(true), функция не вызовет исключения, даже если f1 только объявлена, но не определена; а при добавлении оператора noexcept(false) к функции f2 даже если функция простая На первый взгляд функция также выдает исключение. Когда к функции добавляется описание noexcept (выражение), необходимость создания исключения определяется результатом вычисления выражения. выражение — это константное выражение, которое можно преобразовать в результат типа boo.

//表达式必须是按语境转换为 bool 类型的常量表达式 
int f5(int val) noexcept(val>10) { return 0; }      //error,只能是常量表达式,不能使用参数
template<class T> T f6() noexcept(sizeof(T) < 4);   //OK

         1.2 noexcept и дизайн класса

        Оператор noexcept используется в классе, и если он не нарушает определения и использования класса, работы с определением можно избежать с помощью оператора noexcept.

class A1
{
private:
    /* data */
    int ival;
public:
    A1(int ival_) : ival(ival_){};  //
    A1(/* args */) noexcept;        //由于有其他构造函数,该构造函数可以不定义,类似于=delete
    // ~A1() noexcept; //编译错误,不能和普通函数一样不定义
    ~A1() noexcept{};   //
    void f(int ival_) noexcept;
};
void noexcept_test(void)
{
    A1 a1(10);
    // a1.f(1);    //error,函数未定义
    decltype(a1.f(1)) *p; //OK, f 不求值调用,但需要 noexcept 说明
}

        Функция, которая отличается только спецификацией исключения, не может быть перегружена (аналогично типу возвращаемого значения, спецификация исключения является частью типа функции, но не сигнатурой функции) (начиная с C++17).

class A1
{
private:
    /* data */
    int ival;
public:
    //其他...
    void f1() noexcept;
    // void f1(){};     // 错误,不能重载
    void f2() noexcept(false);
    // void f2(){};     // 错误,不能重载
};

        1.3 noexcept и указатели на функции-члены

        указатели на негенерирующие функции (включая указатели на функции-члены) могут быть назначены или инициализированы (до C++17) неявно преобразуемыми в (начиная с C++17) указатели на функции, которые могут генерировать указатель, но не наоборот.

class A1
{
private:
    /* data */
    int ival;
public:
    //其他...
    //
    void (*pf3)() noexcept; 
    void (*pf4)(); 
};
void fa3() noexcept;
void fa4() {};
void fa5() noexcept{};
//
    A1 a1(10);
    a1.pf3 = fa3;       //error,指向未定义函数
    a1.pf3 = fa4;       //error,要求收窄
    a1.pf3 = fa5;       //OK
    a1.pf4 = fa3;       //error,指向未定义函数
    a1.pf4 = fa4;       //OK
    a1.pf4 = fa5;       //OK

        Следовательно, когда указатели на члены функций класса указывают на внешние функции, указатели с объявлением noexcept должны указывать на функции, определенные как объявление noexcept, а указатели на функции без объявления noexcept могут указывать на обе функции.

        1.4 noexcept и вызовы функций-членов

        Негенерирующие функции позволяют вызывать функции, которые могут вызывать генерацию. Функция std::terminate или std::unexpected (до C++17) вызывается всякий раз, когда генерируется исключение и при поиске блока обработки обнаруживается самый внешний блок функции, которая не вызывает исключение:

class A1
{
private:
    /* data */
    int ival;
public:
    //other...
    void f3(){};
    void g1() noexcept
    {
        f3();   // 合法,即使 f 抛出异常,等效于调用 std::terminate
    }
    // void f4() noexcept; //调用时未定义
    void f4() noexcept{};
    void g2()
    {
        f4();   // 合法,严格收窄
    }
};
//
    A1 a1(10);
    a1.g1();
    a1.g2();

        1.5 noexcept и виртуальные функции

        Если виртуальная функция имеет спецификацию noexcept, указывающую, что она не генерирует исключение, то все объявления (включая определения) каждой из ее переопределенных функций должны быть негенерирующими, если только переопределяющая функция не определена как удаленная:

class Base_noe {
public:
    virtual void f() noexcept;
    virtual void g();
    virtual void h() noexcept = delete;
};
 
class Derived_noe: public Base_noe {
public:
    // void f();              // 谬构:Derived_noe::f 有可能会抛出,Base_noe::f 不会抛出
    void g() noexcept;     // OK,收窄
    // void h() = delete;     // error,overridden function
};

        1.6 требования noexcept и константных выражений

        Выражение e может выдать:

  • e — это вызов функции, указатель на функцию или указатель на функцию-член, которая может вызывать исключение, если только e не является основным константным выражением (до C++17)
  • e выполняет неявный вызов функции, которая может вызвать исключение (например, в качестве перегруженного оператора, функции распределения в новом выражении, конструктора для аргумента функции или деструктора, когда e является полным выражением).
  • e - выражение броска
  • e — это dynamic_cast, который приводит полиморфные ссылочные типы
  • e — это выражение typeid, применяемое к разыменованию указателя на полиморфный тип.
  • e содержит непосредственные подвыражения, которые могут вызывать
class Base_noe1 {
public:
    Base_noe1(int = (Base_noe1(5), 0)) noexcept;
    Base_noe1(const Base_noe1&) noexcept;
    Base_noe1(Base_noe1&&) noexcept;
    ~Base_noe1();
private:
    int ival;
};

int K(){return 1;}
class Base_noe2 {
public:
    Base_noe2() throw();
    Base_noe2(const Base_noe2&) = default; // 隐式异常说明是 noexcept(true)
    Base_noe2(Base_noe2&&, int = (throw K(), 0)) noexcept;
    ~Base_noe2() noexcept(false);
private:
    char cval;
};

class Derived_noe12 : public Base_noe1, public Base_noe2 {
public:
    // Derived_noe12::Derived_noe12() 有可能会抛出,因为 new 运算符
    // Derived_noe12::Derived_noe12(const Derived_noe12&) 不会抛出
    // Derived_noe12::Derived_noe12(D&&) 有可能会抛出:因为 Base_noe2 的构造函数的默认实参有可能会抛出
    // Derived_noe12::~Derived_noe12() 有可能会抛出
 
    // 注意:如果 Base_noe1::~Base_noe1() 为虚,那么此程序将为非良构,因为不会抛出的虚函数的覆盖函数不能抛出
private:
    double dval;
};

        Оператор noexcept для функции не является проверкой во время компиляции; это просто способ для программиста сообщить компилятору, может ли функция генерировать исключение. Компилятор может использовать эту информацию для включения определенных оптимизаций функций, которые не вызывают исключения, и для включения оператора noexcept, который во время компиляции проверяет, объявлено ли конкретное выражение для создания какого-либо исключения. Например, такой контейнер, как std::vector, будет перемещать элемент, если его конструктор перемещения имеет значение noexcept, и копировать элемент в противном случае (если только конструктор копирования недоступен, но перемещение рассматривается в случае гарантии сильного исключения).

2. Пустой класс

        2.1 Пустые классы не пусты

        Пустое определение класса — это ключевое слово class или struct плюс пустое определение тела содержимого {}:

class Empty{};
struct Empty{};

        когда компилятор C++ передает его. Если вы не объявите следующие функции, внимательный компилятор объявит свою собственную версию. Этими функциями являются: конструктор копирования, оператор присваивания, деструктор и пара операторов адреса. Кроме того, если вы не объявите какой-либо конструктор, он также объявит для вас конструктор по умолчанию. Все эти функции являются общедоступными.

class Empty{};
//被编译器扩充为
class Empty {
public:
  Empty();                        // 缺省构造函数
  Empty(const Empty& rhs);        // 拷贝构造函数
  ~Empty();                       // 析构函数
  Empty& operator=(const Empty& rhs);    // 赋值运算符
  Empty* operator&();                    // 取址运算符
  const Empty* operator&() const;
};

        Поэтому пустой класс можно использовать как и большинство обычных классов.Чтобы адреса разных объектов одного типа всегда были разными, требуется, чтобы размер любого объекта или подобъекта-члена был не менее 1, даже если тип является пустым типом класса.

    Empty b1, b2;    //构造
    b1 = b2;        //复制赋值
    b1 = std::move(b2);//移动赋值
    Empty *pb1=new Empty(), *pb2 = nullptr; //构造
    pb2 = pb1;  //
    pb2 = &b1;  //
    *pb2 = b2;  //复制赋值
    pb2 = nullptr;
    delete pb1; //析构
    pb1 = nullptr;
    // 任何空类类型的对象大小至少为 1
    std::cout <<"sizeof(Empty) = "<< sizeof(Empty)<<"\n";   //1

        2.2 Оптимизация пустого базового класса

        Хотя оговорено, что размер пустого типа класса равен 1, когда он используется в качестве подобъекта базового класса, иногда это ограничение не применяется, и его можно полностью оптимизировать вне макета объекта.

//为保证同一类型的不同对象地址始终有别,要求任何对象或成员子对象的大小至少为 1,即使该类型是空的类类型
struct Base {}; // 空类,占用1字节

//如果首个非静态数据成员的类型与一个空基类的类型相同或者由该空基类派生,
//那么禁用空基类优化,因为要求两个同类型基类子对象在最终派生类型的对象表示中必须拥有不同地址。
struct Derived1 : Base {    //空基类被优化
    int i;                  // int成员占用 sizeof(int) 字节
};
//
    // 任何空类类型的对象大小至少为 1
    std::cout <<"sizeof(Base) = "<< sizeof(Base)<<"\n";//1
 
    // 应用空基类优化
    std::cout <<"sizeof(Derived1)  = "<< sizeof(Derived1) <<"\n";//4

        Если тип первого нестатического члена данных такой же, как у пустого базового класса или получен из него, оптимизация пустого базового класса отключается, поскольку в объектном представлении базового класса должны быть два подобъекта одного и того же типа. окончательный производный тип Должен иметь другой адрес.

//为保证同一类型的不同对象地址始终有别,要求任何对象或成员子对象的大小至少为 1,即使该类型是空的类类型
struct Base {}; // 空类,占用1字节

//基类从对象布局中被优化掉
struct Derived : Base { }; //空基类被优化

//如果首个非静态数据成员的类型与一个空基类的类型相同或者由该空基类派生,
//那么禁用空基类优化,因为要求两个同类型基类子对象在最终派生类型的对象表示中必须拥有不同地址。
struct Derived1 : Base {    //空基类被优化
    int i;                  // int成员占用 sizeof(int) 字节
};
 
struct Derived2 : Base {// 不应用空基优化,基类占用1字节
    Base c;             //Base 成员占用1字节,后随2个填充字节以满足 int 的对齐要求
    int i;
};
 
struct Derived3 : Base {//不应用空基类优化,基类占用至少 1 字节加填充
    Derived1 c;         // Derived1成员占用 sizeof(int) 字节
    int i;              // int成员占用 sizeof(int) 字节
};

struct Derived4 : Base {// //空基类被优化
    int i;              // int成员占用 sizeof(int) 字节
    Base c;             //Base 成员占用1字节,后随3个填充字节以满足 int 的对齐要求  
};
//
    // 任何空类类型的对象大小至少为 1
    std::cout <<"sizeof(Base) = "<< sizeof(Base)<<"\n";//1
 
    // 应用空基类优化
    std::cout <<"sizeof(Derived)  = "<< sizeof(Derived) <<"\n";  //1
    std::cout <<"sizeof(Derived1)  = "<< sizeof(Derived1) <<"\n";//4
    std::cout <<"sizeof(Derived2)  = "<< sizeof(Derived2) <<"\n";//8
    std::cout <<"sizeof(Derived3)  = "<< sizeof(Derived3) <<"\n";//12
    std::cout <<"sizeof(Derived4)  = "<< sizeof(Derived4) <<"\n";//8

        2.3 [[no_unique_address]] оптимизировать пустой класс

        Если пустые подобъекты-члены используют атрибут [[no_unique_address]], их можно оптимизировать, как пустые базовые классы. Взятие адреса такого члена дает адрес, который может быть равен какому-то другому члену того же объекта.

//为保证同一类型的不同对象地址始终有别,要求任何对象或成员子对象的大小至少为 1,即使该类型是空的类类型
struct Base {}; // 空类,占用1字节

//如果空成员子对象使用属性 [[no_unique_address]],那么允许像空基类一样优化掉它们。
struct Base1 {
    [[no_unique_address]] Base c1;  //主动优化,空成员c1被优化掉
    Base c2;  // Base,占用 1 字节,后随对 i 的填充      
    int i;    // int成员占用 sizeof(int) 字节      
};
//
    // 应用空基类优化
    std::cout <<"sizeof(Base1)  = "<< sizeof(Base1) <<"\n";      //8

        А оптимизация [[no_unique_address]] по существу достигается за счет обмена адресами с другими установленными участниками:

struct Empty {}; // 空类
 
struct X {
    int i;
    Empty e;    //Empty成员占用1字节,后随3个填充字节以满足 int 的对齐要求 
};
 
struct Y {
    int i;
    [[no_unique_address]] Empty e;//主动优化,空成员e被优化掉
};
 
struct Z {
    char c;
    //e1 与 e2 不能共享同一地址,因为它们拥有相同类型,其中一者可以与 c 共享地址
    [[no_unique_address]] Empty e1, e2;//主动优化,空成员e1、e2其中一个被优化掉
};
 
struct W {
    char c[2];
    //e1 与 e2 不能拥有同一地址,但它们之一能与 c[0] 共享,而另一者与 c[1] 共享
    [[no_unique_address]] Empty e1, e2;//主动优化,空成员e1、e2被优化掉
};
//
void no_unique_address_test(void)
{
    // 任何空类类型对象的大小至少为 1
    std::cout <<"sizeof(Empty)  = "<< sizeof(Empty) <<"\n";      //1
 
    // 至少需要多一个字节以给 e 唯一地址
    std::cout <<"sizeof(X)  = "<< sizeof(X) <<"\n"; //8
 
    // 优化掉空成员
    std::cout <<"sizeof(Y)  = "<< sizeof(Y) <<"\n";//4 

    // e1 与 e2 不能共享同一地址,因为它们拥有相同类型,尽管它们标记有 [[no_unique_address]]。
    // 然而,其中一者可以与 c 共享地址。
    std::cout <<"sizeof(Z)  = "<< sizeof(Z) <<"\n";//2
 
    // e1 与 e2 不能拥有同一地址,但它们之一能与 c[0] 共享,而另一者与 c[1] 共享
    std::cout <<"sizeof(W)  = "<< sizeof(W) <<"\n";//3
};

Три, явный спецификатор

        3.1 форма явного синтаксиса

        Явный спецификатор может появляться только в последовательности спецификаторов объявления конструктора или функции преобразования (начиная с C++11) в определении класса, форма явного синтаксиса:

/*
*指定构造函数或转换函数 (C++11 起)或推导指引(C++17 起)为显式,
*即它不能用于隐式转换和复制初始化。
*/
explicit

/*explicit 说明符可以与常量表达式一同使用。
*函数在且只会在该常量表达式求值为 true 时是显式的。 (C++20 起)
*表达式 - 经按语境转换为 bool 类型的常量表达式  
*/
explicit ( 表达式 )     //(C++20 起)

        Конструктор (до C++11), объявленный без явного спецификатора функции и имеющий единственный параметр без значения по умолчанию, называется конструктором преобразования.

        3.2 Конструктор преобразования

        В отличие от явных конструкторов, которые учитываются только при прямой инициализации (включая явные преобразования, такие как static_cast), конструкторы преобразования также учитываются при инициализации копирования как часть определенных пользователем последовательностей преобразования. Обычно говорят, что конструктор преобразования задает неявное преобразование из его типа аргумента (если есть) в его тип класса. Обратите внимание, что неявные пользовательские функции преобразования также задают неявное преобразование. Неявно объявленные и определяемые пользователем неявные конструкторы копирования и перемещения также являются конструкторами преобразования.

struct A_common
{
    A_common(int) { std::cout <<"A_common(int)\n";}      // 转换构造函数
    A_common(int, int) { std::cout <<"A_common(int,int)\n";} // 转换构造函数(C++11)
    operator bool() const { std::cout <<"A_common bool()\n";return true; }
};

        И конструкторы (кроме копирования и перемещения), и определяемые пользователем функции преобразования могут быть шаблонами функций; смысл добавления явных значений не меняется.

//explicit 说明符只能在类定义之内的构造函数或转换函数 (C++11 起)的 声明说明符序列中出现。
struct B_explicit
{
    explicit B_explicit(int) { std::cout <<"B_explicit(int)\n";}
    explicit B_explicit(int, int) { std::cout <<"B_explicit(int,int)\n";}
    explicit operator bool() const { std::cout <<"B_explicit bool()\n";return true; }
};

        3.3 явные ограничения использования

         У явного ключевого слова есть два основных применения:

  •  Указывает, что конструктор или функция преобразования (начиная с C++11) являются явными, т. е. их нельзя использовать для неявных преобразований и инициализации копирования.
  •  Может использоваться с константными выражениями.Когда константное выражение истинно, это явное преобразование (начиная с C++20).

void explicit_test(void)
{
    A_common a1 = 1;      // OK:复制初始化选择 A_common::A_common(int)
    A_common a2(2);       // OK:直接初始化选择 A_common::A_common(int)
    A_common a3 {4, 5};   // OK:直接列表初始化选择 A_common::A_common(int, int)
    A_common a4 = {4, 5}; // OK:复制列表初始化选择 A_common::A_common(int, int)
    A_common a5 = (A_common)1;    // OK:显式转型进行 static_cast
    if (a1) ;               // OK:A_common::operator bool()
    bool na1 = a1;          // OK:复制初始化选择 A_common::operator bool()
    bool na2 = static_cast<bool>(a1); // OK:static_cast 进行直接初始化
 
//  B_explicit b1 = 1;      // 错误:复制初始化不考虑 B_explicit::B_explicit(int)
    B_explicit b2(2);       // OK:直接初始化选择 B_explicit::B_explicit(int)
    B_explicit b3 {4, 5};   // OK:直接列表初始化选择 B_explicit::B_explicit(int, int)
//  B_explicit b4 = {4, 5}; // 错误:复制列表初始化不考虑 B_explicit::B_explicit(int,int)
    B_explicit b5 = (B_explicit)1;     // OK:显式转型进行 static_cast
    if (b2) ;               // OK:B_explicit::operator bool()
//  bool nb1 = b2;          // 错误:复制初始化不考虑 B_explicit::operator bool()
    bool nb2 = static_cast<bool>(b2);  // OK:static_cast 进行直接初始化
}

        3.4 явные и константные выражения

        Начиная со стандарта C++20, ключевое слово absolute может использоваться вместе с композицией выражения, и определяется, требуется ли явная конструкция, на основе возвращаемого значения выражения.

//explicit 说明符可以与常量表达式一同使用。函数在且只会在该常量表达式求值为 true 时是显式的。
explicit ( 表达式 )     //(C++20 起) 

        Явное ключевое слово может использоваться с константным выражением constexpr. Однако, если константное выражение оценивается как истинное, конструктор является явным.

constexpr bool cexpr1 = false;
constexpr bool cexpr2 = true;

class C_explicit {
public:
    explicit(cexpr1) C_explicit(int) {}; //c++20
};
class D_explicit {
public:
    explicit(cexpr2) D_explicit(int) {}; //c++20
};
//
    C_explicit ec1=1;   //OK
    C_explicit ec2(1);
    // D_explicit ec3=1;   //error
    D_explicit ec4(1);

Четыре, спецификатор constexpr

        4.1 Спецификатор constexpr и const

        Начиная с C++11, предоставляется спецификатор constexpr, который указывает, что значение переменной или функции может появляться в константном выражении. Спецификатор constexpr объявляет, что функция или переменная могут принимать значение во время компиляции. Эти переменные и функции (при наличии соответствующих аргументов функции) можно использовать там, где требуются константные выражения времени компиляции.

// 输出要求编译时常量的函数,用于测试
template<int n>
class constN
{
public:
    constN() { std::cout << n << '\n'; }
};
//
    int number = 10;
    constN<number> cnumber;  //error,不能作为常量表达
    constexpr int num = 10;
    constN<num> cnum;  // OK,在编译时计算
    const int numc = 10;
    constN<numc> cnumc;  //OK

        На первый взгляд, он ничем не отличается от const, но в стандарте C++11 для решения двойной семантической проблемы ключевого слова const семантика const, представляющая «только для чтения», сохранена, а семантика «константа» назначается вновь добавленному ключевому слову constexpr. Поэтому рекомендуется разделить функции const и constexpr, то есть все сценарии, выражающие семантику «только для чтения», используют const, а сценарии, выражающие «константную» семантику, используют constexpr.

constexpr int sqr1(int arg){
    return arg*arg;
}
const int sqr2(int arg){
    return arg*arg;
}
#include <array>
//
    std::array<int,sqr1(3)> a1;
    // std::array<int,sqr2(3)> a2;//error: call to non-'constexpr' function 'const int sqr2(int)'

        В приведенном выше коде, поскольку возвращаемое значение функции sqr2() только дополнено const, но не дополнено более явным constexpr, его нельзя использовать для инициализации контейнера массива (только константы могут инициализировать контейнер массива).

        4.2 Требования к использованию constexpr

        Переменные, определенные constexpr, должны быть немедленно инициализированы, а полное выражение их инициализации, включая все неявные преобразования, вызовы конструкторов и т. д., должно быть константным выражением:

  • Его тип должен быть буквальным типом (LiteralType).
  • Он должен быть немедленно инициализирован.
  • Полное выражение его инициализации, включая все неявные преобразования, вызовы конструктора и т. д., должно быть константным выражением.
  • он должен иметь постоянный деструктор
// C++11 constexpr 函数使用递归而非迭代
// C++14 constexpr 函数可使用局部变量和循环
constexpr int factorial(int n)
{
    return n <= 1 ? 1 : (n * factorial(n - 1));
}
//
    constexpr int num1; //error: uninitialized 'const num1' [-fpermissive]
    num1 = 100;         //error: assignment of read-only variable 'num1'
    constexpr int num = 10;
    constexpr int num2 = 2*num;           //OK
    constexpr int num3 = factorial(3);    //OK

        Функция, определенная constexpr, должна соответствовать следующим условиям:

[1] Он должен быть невиртуальным, невиртуальные константы недоступны (до C++20)

class constexprTest
{
public:
    constexpr virtual void f(){};   //c++20前,error,在此模式下,函数不能同时为 constexpr 和 virtual
};

[2] Это не должна быть сопрограмма, а сопрограмма обычно недостижима с помощью констант (начиная с C++20).

#include <coroutine>
class constexprTest
{
public:
    constexpr task coroutine_func(int n = 0)  //error: 'co_return' cannot be used in a 'constexpr' function
    {
        co_return; 
    }
};

[3] Тип возвращаемого значения (если он присутствует) должен быть литеральным типом (LiteralType).

class Empty{};
class Test1
{
private:
    char* pc;
public:
    Test1(/* args */){pc = new char[10];};
    ~Test1(){delete[] pc; pc = nullptr;};
};
//
class constexprTest
{
public:
    constexpr Empty f() //OK
    {
        return Empty();
    }
    constexpr Test1 g()//error: invalid return type 'Test1' of 'constexpr' function 'constexpr Test1 constexprTest::g()'
    {
        return Test1();
    }
};

[4] Каждый из его параметров должен быть литеральным типом (LiteralType)
[5] Для конструкторов и деструкторов (начиная с C++20) класс не должен иметь виртуального базового класса

lass constexprTest : virtual Empty //虚继承影响constexpr的构造和析构
{
public:
    constexpr constexprTest(){};    //error: 'class constexprTest' has virtual base classes
    constexpr ~constexprTest(){};   //c++20前,析构函数不能是 constexpr
};

[6] Существует по крайней мере один набор значений аргументов, такой, что вызов функции является оцениваемым подвыражением основного константного выражения (достаточно для конструктора для инициализатора константы) (начиная с C++14). Диагностировать, нарушено ли это, не требуется.

[7] Тело функции не должно быть блоком проверки функции (до C++20).

class constexprTest
{
public:
    constexpr void g3(){
        try //error 语句不能出现在 constexpr 函数中
        {
            /* code */
        }
        catch(const std::exception& e)
        {
            std::cerr << e.what() << '\n';
        }
        
    }
};

[8] Тело функции должно быть отброшено или предварительно установлено, или содержать только следующее содержимое (до C++14): 1) пустой оператор (только точка с запятой); 2) оператор static_assert; 3) typedef, который не определяет класс или объявление перечисления и объявление псевдонима; 4) объявление использования; 5) директива использования; 6) ровно один оператор возврата, если функция не является конструктором.
◦Тело функции не должно содержать (начиная с C++14, до C++203): 1) оператор goto, 2) операторы с метками, отличными от case и default, 3) (до C++20) блок try, 4 ) (до C++20) оператор asm; 5) (до C++20) определение переменной без инициализации; 6) определение переменной нелитерального типа; 7) определение статической переменной или переменной периода хранения потока; 8) (A тело функции =default; или =delete; не содержит ничего из вышеперечисленного.)

[9] Конструктор constexpr, телом функции которого не является =delete;, должен удовлетворять следующим дополнительным требованиям: 1) Для конструктора класса или структуры каждый подобъект и каждый невариантный нестатический элемент данных должны быть инициализированы. Если класс представляет собой класс в стиле объединения, для каждого непустого анонимного члена объединения должен быть инициализирован ровно один вариантный член (до C++20); 2) для конструктора непустого объединения должно быть ровно один Нестатические элементы данных инициализируются (до C++20); 3) Каждый конструктор, выбранный для инициализации нестатических элементов и базовых классов, должен быть конструктором constexpr.
[10] Деструктор не может быть constexpr, но обычный деструктор может быть неявно вызван в константном выражении. (до С++ 20)

[11] Деструктор constexpr тела функции not=delete; должен соответствовать следующим дополнительным требованиям: ◦Каждый деструктор, используемый для уничтожения нестатических элементов данных и базовых классов, должен быть деструктором constexpr. (начиная с С++ 20)

        4.3 Использование constexpr

        Пожалуйста, помните, что пока существует constexpr, он должен быть достижим с помощью константного выражения, то есть постоянный результат может быть вычислен во время компиляции. Если переменная изменена, переменная постоянна и достижима. Если функция изменен, он вернется, и можно будет достичь константы формального параметра. , и можно будет определить тело функции.

#include <stdexcept>
// C++11 constexpr 函数使用递归而非迭代
// C++14 constexpr 函数可使用局部变量和循环
constexpr int factorial(int n)
{
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

// 输出要求编译时常量的函数,用于测试
template<int n>
class constN
{
public:
    constN() { std::cout << n << '\n'; }
};

// 字面类
class conststr {
    const char* p;
    std::size_t sz;
public:
    template<std::size_t N>
    constexpr conststr(const char(&a)[N]): p(a), sz(N - 1) {}
 
    // constexpr 函数通过抛异常来提示错误
    // C++11 中,它们必须用条件运算符 ?: 来这么做
    constexpr char operator[](std::size_t n) const
    {
        return n < sz ? p[n] : throw std::out_of_range("");
    }
    constexpr std::size_t size() const { return sz; }
};
 
// C++11 constexpr 函数必须把一切放在单条 return 语句中
// (C++14 无此要求)
constexpr std::size_t countlower(conststr s, std::size_t n = 0,
                                             std::size_t c = 0)
{
    return n == s.size() ? c :
           'a' <= s[n] && s[n] <= 'z' ? countlower(s, n + 1, c + 1) :
                                        countlower(s, n + 1, c);
};
//
    std::cout << "4! = " ;
    constN<factorial(4)> out1;  // 在编译时计算
 
    volatile int k = 8;         // 使用 volatile 防止优化
    std::cout << k << "! = " << factorial(k) << '\n'; // 运行时计算   

    std::cout << "the number of lowercase letters in \"Hello, world!\" is ";
    constN<countlower("Hello, world!")> out2; // 隐式转换到常量字符串
//out log
4! = 24
8! = 40320
the number of lowercase letters in "Hello, world!" is 9

       Введение этой статьи закончено!

        Спасибо, читатели, что терпеливо дочитали до конца.Мои статьи очень длинные и по пунктам очень подробно.Возможны упущения и ошибки.Если есть какие-то неточности, прошу указать на них. Если контент вас затронул, поставьте лайк и подпишитесь на него, чтобы не потеряться.

5. Дополнение к исходному коду

        Инструкции по компиляции: g++ main.cpp test*.cpp -o test.exe -std=c++20 (или c++11, c++17)

        main.cpp

#include "test1.h"

int main(int argc, char* argv[])
{
    noexcept_test();
    compile_test();
    empty_class_test();
    no_unique_address_test();
    explicit_test();
    return 0;
}

        test1.h

#ifndef _TEST_1_H_
#define _TEST_1_H_

void noexcept_test(void);
void compile_test(void);
void empty_class_test(void);
void no_unique_address_test(void);
void explicit_test(void);
#endif //_TEST_1_H_

        test1.cpp

#include "test1.h"

#include <iostream>
int f1() noexcept; 
int f2() noexcept(false) { return 0; }
int f3(int val) noexcept(__cplusplus > 201103L){ return val/2; }
// f4 是否声明为 noexcept 取决于 T() 是否会抛出异常
template <class T> int f4() noexcept(noexcept(T())) { return 0;}
// int f5(int val) noexcept(val>10) { return 0; }  //error,只能是常量表达式,不能使用参数
template<class T> T f6() noexcept(sizeof(T) < 4);   //OK

class A1
{
private:
    /* data */
    int ival;
public:
    A1(int ival_) : ival(ival_){};  //
    A1(/* args */) noexcept;
    // ~A1() noexcept; //编译错误,不能和普通函数一样不定义
    ~A1() noexcept{};   //
    void f(int ival_) noexcept;
    //
    void f1() noexcept;
    // void f1(){};    // 错误:不算重载
    void f2() noexcept(false);
    // void f2(){};    // 错误:不算重载
    //
    void (*pf3)() noexcept; 
    void (*pf4)();
    void f3(){};
    void g1() noexcept
    {
        f3();   // 合法,即使 f 抛出异常,等效于调用 std::terminate
    }
    // void f4() noexcept; //调用时未定义
    void f4() noexcept{};
    void g2()
    {
        f4();   // 合法,严格收窄
    }
};
void fa3() noexcept;
void fa4() {};
void fa5() noexcept{};

class Base_noe {
public:
    virtual void f() noexcept;
    virtual void g();
    virtual void h() noexcept = delete;
};
 
class Derived_noe: public Base_noe {
public:
    // void f();              // 谬构:Derived_noe::f 有可能会抛出,Base_noe::f 不会抛出
    void g() noexcept;     // OK,收窄
    // void h() = delete;     // error,overridden function
};

class Base_noe1 {
public:
    Base_noe1(int = (Base_noe1(5), 0)) noexcept;
    Base_noe1(const Base_noe1&) noexcept;
    Base_noe1(Base_noe1&&) noexcept;
    ~Base_noe1();
private:
    int ival;
};

int K(){return 1;}
class Base_noe2 {
public:
    Base_noe2() throw();
    Base_noe2(const Base_noe2&) = default; // 隐式异常说明是 noexcept(true)
    Base_noe2(Base_noe2&&, int = (throw K(), 0)) noexcept;
    ~Base_noe2() noexcept(false);
private:
    char cval;
};

class Derived_noe12 : public Base_noe1, public Base_noe2 {
public:
    // Derived_noe12::Derived_noe12() 有可能会抛出,因为 new 运算符
    // Derived_noe12::Derived_noe12(const Derived_noe12&) 不会抛出
    // Derived_noe12::Derived_noe12(D&&) 有可能会抛出:因为 Base_noe2 的构造函数的默认实参有可能会抛出
    // Derived_noe12::~Derived_noe12() 有可能会抛出
 
    // 注意:如果 Base_noe1::~Base_noe1() 为虚,那么此程序将为非良构,因为不会抛出的虚函数的覆盖函数不能抛出
private:
    double dval;
};

void noexcept_test(void)
{
    bool b1 = noexcept(f1());     
    std::cout << "b1 = " << b1 << "\n" ;// true 
    bool b2 = noexcept(f2());     
    std::cout << "b2 = " << b2 << "\n" ;// false
    bool b3 = noexcept(f3(10));   
    std::cout << "b3 = " << b3 << "\n" ;// true,c++11后,false,c++11
    bool b4 = noexcept(f4<int>());     
    std::cout << "b4 = " << b4 << "\n" ;// true

    A1 a1(10);
    // a1.f(1);    //error,函数未定义
    decltype(a1.f(1)) *p; //OK, f 不求值调用,但需要 noexcept 说明
    // a1.pf3 = fa3;       //error,指向未定义函数
    // a1.pf3 = fa4;       //error,要求收窄
    a1.pf3 = fa5;       //OK
    // a1.pf4 = fa3;       //error,指向未定义函数
    a1.pf4 = fa4;       //OK
    a1.pf4 = fa5;       //OK
    //
    a1.g1();
    a1.g2();
}

//为保证同一类型的不同对象地址始终有别,要求任何对象或成员子对象的大小至少为 1,即使该类型是空的类类型
struct Base {}; // 空类,占用1字节

//如果空成员子对象使用属性 [[no_unique_address]],那么允许像空基类一样优化掉它们。
struct Base1 {
    [[no_unique_address]] Base c1;  //主动优化,空成员c1被优化掉
    Base c2;  // Base,占用 1 字节,后随对 i 的填充      
    int i;    // int成员占用 sizeof(int) 字节      
};

//基类从对象布局中被优化掉
struct Derived : Base { }; //空基类被优化

//如果首个非静态数据成员的类型与一个空基类的类型相同或者由该空基类派生,
//那么禁用空基类优化,因为要求两个同类型基类子对象在最终派生类型的对象表示中必须拥有不同地址。
struct Derived1 : Base {    //空基类被优化
    int i;                  // int成员占用 sizeof(int) 字节
};
 
struct Derived2 : Base {// 不应用空基优化,基类占用1字节
    Base c;             //Base 成员占用1字节,后随2个填充字节以满足 int 的对齐要求
    int i;
};
 
struct Derived3 : Base {//不应用空基类优化,基类占用至少 1 字节加填充
    Derived1 c;         // Derived1成员占用 sizeof(int) 字节
    int i;              // int成员占用 sizeof(int) 字节
};

struct Derived4 : Base {//空基类被优化
    int i;              // int成员占用 sizeof(int) 字节
    Base c;             //Base 成员占用1字节,后随3个填充字节以满足 int 的对齐要求  
};

struct Empty {}; // 空类
 
struct X {
    int i;
    Empty e;    //Empty成员占用1字节,后随3个填充字节以满足 int 的对齐要求 
};
 
struct Y {
    int i;
    [[no_unique_address]] Empty e;//主动优化,空成员e被优化掉
};
 
struct Z {
    char c;
    //e1 与 e2 不能共享同一地址,因为它们拥有相同类型,其中一者可以与 c 共享地址
    [[no_unique_address]] Empty e1, e2;//主动优化,空成员e1、e2其中一个被优化掉
};
 
struct W {
    char c[2];
    //e1 与 e2 不能拥有同一地址,但它们之一能与 c[0] 共享,而另一者与 c[1] 共享
    [[no_unique_address]] Empty e1, e2;//主动优化,空成员e1、e2被优化掉
};

void empty_class_test(void)
{
    Empty b1, b2;    //构造
    b1 = b2;        //复制赋值
    b1 = std::move(b2);//移动赋值
    Empty *pb1=new Empty(), *pb2 = nullptr; //构造
    pb2 = pb1;  //
    pb2 = &b1;  //
    *pb2 = b2;  //复制赋值
    pb2 = nullptr;
    delete pb1; //析构
    pb1 = nullptr;
    // 任何空类类型的对象大小至少为 1
    std::cout <<"sizeof(Empty) = "<< sizeof(Empty)<<"\n";   //1

    // 任何空类类型的对象大小至少为 1
    std::cout <<"sizeof(Base) = "<< sizeof(Base)<<"\n";//1
 
    // 应用空基类优化
    std::cout <<"sizeof(Base1)  = "<< sizeof(Base1) <<"\n";      //8
    std::cout <<"sizeof(Derived)  = "<< sizeof(Derived) <<"\n";  //1
    std::cout <<"sizeof(Derived1)  = "<< sizeof(Derived1) <<"\n";//4
    std::cout <<"sizeof(Derived2)  = "<< sizeof(Derived2) <<"\n";//8
    std::cout <<"sizeof(Derived3)  = "<< sizeof(Derived3) <<"\n";//12
    std::cout <<"sizeof(Derived4)  = "<< sizeof(Derived4) <<"\n";//8
}

void no_unique_address_test(void)
{
    // 任何空类类型对象的大小至少为 1
    std::cout <<"sizeof(Empty)  = "<< sizeof(Empty) <<"\n";      //1
 
    // 至少需要多一个字节以给 e 唯一地址
    std::cout <<"sizeof(X)  = "<< sizeof(X) <<"\n"; //8
 
    // 优化掉空成员
    std::cout <<"sizeof(Y)  = "<< sizeof(Y) <<"\n";//4 

    // e1 与 e2 不能共享同一地址,因为它们拥有相同类型,尽管它们标记有 [[no_unique_address]]。
    // 然而,其中一者可以与 c 共享地址。
    std::cout <<"sizeof(Z)  = "<< sizeof(Z) <<"\n";//2
 
    // e1 与 e2 不能拥有同一地址,但它们之一能与 c[0] 共享,而另一者与 c[1] 共享
    std::cout <<"sizeof(W)  = "<< sizeof(W) <<"\n";//3
};

struct A_common
{
    A_common(int) { std::cout <<"A_common(int)\n";}      // 转换构造函数
    A_common(int, int) { std::cout <<"A_common(int,int)\n";} // 转换构造函数(C++11)
    operator bool() const { std::cout <<"A_common bool()\n";return true; }
};
//explicit 说明符只能在类定义之内的构造函数或转换函数 (C++11 起)的 声明说明符序列中出现。
struct B_explicit
{
    explicit B_explicit(int) { std::cout <<"B_explicit(int)\n";}
    explicit B_explicit(int, int) { std::cout <<"B_explicit(int,int)\n";}
    explicit operator bool() const { std::cout <<"B_explicit bool()\n";return true; }
};

constexpr bool cexpr1 = false;
constexpr bool cexpr2 = true;

class C_explicit {
public:
    explicit(cexpr1) C_explicit(int) {}; //c++20
};
class D_explicit {
public:
    explicit(cexpr2) D_explicit(int) {}; //c++20
};


void explicit_test(void)
{
    A_common a1 = 1;      // OK:复制初始化选择 A_common::A_common(int)
    A_common a2(2);       // OK:直接初始化选择 A_common::A_common(int)
    A_common a3 {4, 5};   // OK:直接列表初始化选择 A_common::A_common(int, int)
    A_common a4 = {4, 5}; // OK:复制列表初始化选择 A_common::A_common(int, int)
    A_common a5 = (A_common)1;    // OK:显式转型进行 static_cast
    if (a1) ;               // OK:A_common::operator bool()
    bool na1 = a1;          // OK:复制初始化选择 A_common::operator bool()
    bool na2 = static_cast<bool>(a1); // OK:static_cast 进行直接初始化
 
//  B_explicit b1 = 1;      // 错误:复制初始化不考虑 B_explicit::B_explicit(int)
    B_explicit b2(2);       // OK:直接初始化选择 B_explicit::B_explicit(int)
    B_explicit b3 {4, 5};   // OK:直接列表初始化选择 B_explicit::B_explicit(int, int)
//  B_explicit b4 = {4, 5}; // 错误:复制列表初始化不考虑 B_explicit::B_explicit(int,int)
    B_explicit b5 = (B_explicit)1;     // OK:显式转型进行 static_cast
    if (b2) ;               // OK:B_explicit::operator bool()
//  bool nb1 = b2;          // 错误:复制初始化不考虑 B_explicit::operator bool()
    bool nb2 = static_cast<bool>(b2);  // OK:static_cast 进行直接初始化
    //
    C_explicit ec1=1;   //OK
    C_explicit ec2(1);
    // D_explicit ec3=1;   //error
    D_explicit ec4(1);
}

#include <stdexcept>
// C++11 constexpr 函数使用递归而非迭代
// C++14 constexpr 函数可使用局部变量和循环
constexpr int factorial(int n)
{
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

// 输出要求编译时常量的函数,用于测试
template<int n>
class constN
{
public:
    constN() { std::cout << n << '\n'; }
};

// 字面类
class conststr {
    const char* p;
    std::size_t sz;
public:
    template<std::size_t N>
    constexpr conststr(const char(&a)[N]): p(a), sz(N - 1) {}
 
    // constexpr 函数通过抛异常来提示错误
    // C++11 中,它们必须用条件运算符 ?: 来这么做
    constexpr char operator[](std::size_t n) const
    {
        return n < sz ? p[n] : throw std::out_of_range("");
    }
    constexpr std::size_t size() const { return sz; }
};
 
// C++11 constexpr 函数必须把一切放在单条 return 语句中
// (C++14 无此要求)
constexpr std::size_t countlower(conststr s, std::size_t n = 0,
                                             std::size_t c = 0)
{
    return n == s.size() ? c :
           'a' <= s[n] && s[n] <= 'z' ? countlower(s, n + 1, c + 1) :
                                        countlower(s, n + 1, c);
};

constexpr int sqr1(int arg){
    return arg*arg;
}
const int sqr2(int arg){
    return arg*arg;
}
#include <array>
#include <coroutine>
// struct task {
//     struct promise_type {
//         void return_void() {}   //co_return调用
//         void unhandled_exception() {
//             std::cout << "task::promise_type.unhandled_exception \n";
//         }
//     };
//     using Handle = std::coroutine_handle<promise_type>;//协程句柄
//     explicit task(Handle coroutine) : m_coroutine{coroutine} {} //get_return_object时调用
//     task() = default;
//     ~task() { 
//         std::cout << "~task \n";
//         if (m_coroutine) //自行销毁
//         {
//             m_coroutine.destroy(); 
//         }
//     }
//     // task(const task&) = delete;
//     // task& operator=(const task&) = delete;
//     Handle m_coroutine; //协程句柄
// };

class Test1
{
private:
    /* data */
    char* pc;
public:
    Test1(/* args */);
    ~Test1();
};

Test1::Test1(/* args */)
{
    pc = new char[10];
}

Test1::~Test1()
{
    delete[] pc; pc = nullptr;
}

class constexprTest : virtual Empty //虚继承影响constexpr的构造和析构
{
public:
    // constexpr constexprTest(){};    //error: 'class constexprTest' has virtual base classes
    // constexpr ~constexprTest(){};   //c++20前,析构函数不能是 constexpr
    // constexpr virtual void f(){};   //error,在此模式下,函数不能同时为 constexpr 和 virtual
    // constexpr task coroutine_func(int n = 0)  //error: 'co_return' cannot be used in a 'constexpr' function
    // {
    //     co_return; 
    // }
    constexpr Empty f()
    {
        return Empty();
    }
    // constexpr Test1 g()//error: invalid return type 'Test1' of 'constexpr' function 'constexpr Test1 constexprTest::g()'
    // {
    //     return Test1();
    // }
    constexpr void g1(const Test1& obj){};
    // constexpr void g3(){
    //     try //error 语句不能出现在 constexpr 函数中
    //     {
    //         /* code */
    //     }
    //     catch(const std::exception& e)
    //     {
    //         std::cerr << e.what() << '\n';
    //     }
        
    // }
};

void compile_test(void)
{
    int number = 10;
    // constN<number> cnumber;  //error
    constexpr int num = 10;
    constN<num> cnum;  // OK,在编译时计算
    const int numc = 10;
    constN<numc> cnumc;  //OK
    //
    std::array<int,sqr1(3)> a1;
    // std::array<int,sqr2(3)> a2;//error: call to non-'constexpr' function 'const int sqr2(int)'
    //
    // constexpr int num1; //error: uninitialized 'const num1' [-fpermissive]
    // num1 = 100;         //error: assignment of read-only variable 'num1'
    constexpr int num2 = 2*num;
    constexpr int num3 = factorial(3);

    std::cout << "4! = " ;
    constN<factorial(4)> out1;  // 在编译时计算
 
    volatile int k = 8;         // 使用 volatile 防止优化
    std::cout << k << "! = " << factorial(k) << '\n'; // 运行时计算   

    std::cout << "the number of lowercase letters in \"Hello, world!\" is ";
    constN<countlower("Hello, world!")> out2; // 隐式转换到常量字符串
};

template<typename T>
class Base3
{
protected:
    int var;
};
 
template<typename T>
class Derived5 : public Base3<T>
{
public:
    Derived5()
    {
        // var = 1;    // 错误: 'var' 未在此作用域中声明
        this->var = 1; // OK
    }
};

class T
{
    int x;
 
    void foo()
    {
        x = 6;       // 等同于 this->x = 6;
        this->x = 5; // 显式使用 this->
    }
 
    void foo() const
    {
//        x = 7; // 错误:*this 是常量
    }
 
    void foo(int x) // 形参 x 遮蔽拥有相同名字的成员
    {
        this->x = x; // 无限定的 x 代表形参
                     // 需要用‘this->’消歧义
    }
 
    int y;
    T(int x) : x(x), // 用形参 x 初始化成员 x
               y(this->x) // 用成员 x 初始化成员 y
    {}
 
    T& operator= ( const T& b )
    {
        x = b.x;
        return *this; // 许多重载运算符都返回 *this
    }
};
 
class Outer {
    // int a[sizeof(*this)]; // 错误:不在成员函数中
    unsigned int sz = sizeof(*this); // OK:在默认成员初始化器中
    void f() {
        int b[sizeof(*this)]; // OK
        struct Inner {
            // int c[sizeof(*this)]; // 错误:不在 Inner 的成员函数中
            void f()
            {
                int c[sizeof(*this)];// OK
            };
        };
    }
};

рекомендация

отblog.csdn.net/py8105/article/details/129685524