c/c++ development, unavoidable custom class type (Part 7). A few taels of silver

Table of contents

1. Noexcept specifier and class member function

        1.1 noexcept grammar and application

         1.2 noexcept and class design

        1.3 noexcept and member function pointers

        1.4 noexcept and member function calls

        1.5 noexcept and virtual functions

        1.6 noexcept and constant expression requirements

2. Empty class

        2.1 Empty classes are not empty

        2.2 Empty base class optimization

        2.3 [[no_unique_address]] optimize empty class

Three, explicit specifier

        3.1 explicit syntax form

        3.2 Conversion constructor

        3.3 explicit usage constraints

        3.4 explicit and constant expressions

Four, constexpr specifier

        4.1 constexpr specifier and const

        4.2 constexpr usage requirements

        4.3 The use of constexpr

5. Source code supplement


1. Noexcept specifier and class member function

        Since c++11, the standard library has introduced the noexcept specifier, which is intended to replace throw and is used to specify whether the function throws an exception.

        1.1 noexcept grammar and application

        The syntax of the noexcept specifier is as follows:

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

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

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

        The noexcept specification is part of the function type and can appear as part of any function declarator. Every function specified by noexcept either does not throw, or has the potential to throw.

#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 can also be used as an operator, which is checked at compile time, becomes part of an expression, and returns true if the expression does not throw any exceptions. The noexcept operator does not evaluate expressions. The result is true if the expression's set of potential exceptions is empty (until C++17) and the expression does not throw (since C++17), otherwise the result is false.

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
}

        When adding a noexcept statement to a function, that is, noexcept(true), the function will not throw an exception, even if f1 is only declared but not defined; and when adding a noexcept(false) statement to a function f2, even if the function is a simple one At a glance the function also throws an exception. When the noexcept (expression) description is added to the function, whether to throw an exception is determined by the result of the expression calculation. expression is a constant expression that can be converted to a boo result.

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

         1.2 noexcept and class design

        The noexcept statement is used in a class, and if it does not violate the definition and usage of the class, the definition work can be avoided through the noexcept statement.

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 说明
}

        A function that differs only in the exception specification cannot be overloaded (similar to the return type, the exception specification is part of the function type, but not the function signature) (since C++17).

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

        1.3 noexcept and member function pointers

        pointers to non-throwing functions (including pointers to member functions) can be assigned to or initialized with (until C++17) implicitly convertible to (since C++17) pointers to functions that may throw pointer, but not vice versa.

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

        Therefore, when class function member pointers point to external functions, those with noexcept declaration must point to functions defined as noexcept declaration, and function pointers without noexcept declaration can point to both.

        1.4 noexcept and member function calls

        Non-throwing functions allow calls to functions that may throw. The function std::terminate or std::unexpected (until C++17) is called whenever an exception is thrown and the lookup of the handling block encounters the outermost block of a function that does not throw:

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 and virtual functions

        If a virtual function has a noexcept specification indicating that it does not throw, then all declarations (including definitions) of each of its overridden functions must be non-throwing, unless the overriding function is defined as deleted:

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 and constant expression requirements

        The expression e may throw:

  • e is a call to a function, pointer to function, or pointer to member function that may throw, unless e is a core constant expression (until C++17)
  • e makes an implicit call to a function that might throw (for example, as an overloaded operator, an allocation function in a new expression, a constructor for a function argument, or a destructor when e is a full expression)
  • e is a throw expression
  • e is a dynamic_cast that casts polymorphic reference types
  • e is a typeid expression applied to the dereference of a pointer to a polymorphic type
  • e contains immediate subexpressions that may throw
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;
};

        A noexcept statement on a function is not a compile-time check; it's simply a way for the programmer to tell the compiler whether a function can throw an exception. The compiler can use this information to enable certain optimizations on functions that do not throw, and to enable the noexcept operator, which checks at compile time whether a particular expression is declared to throw any exception. For example, a container such as std::vector will move the element if the element's move constructor is noexcept, otherwise it will copy the element (unless the copy constructor is not accessible, but the move constructor that may throw will only return Considered in case of strong exception guarantee).

2. Empty class

        2.1 Empty classes are not empty

        An empty class definition is the keyword class or struct plus an empty content body {} definition:

class Empty{};
struct Empty{};

        when the C++ compiler passes it through. If you don't declare the following functions, a considerate compiler will declare its own version. These functions are: a copy constructor, an assignment operator, a destructor, and a pair of address-of operators. Also, if you don't declare any constructor, it will also declare a default constructor for you. All these functions are public.

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

        Therefore, the empty class can be used like most ordinary classes. To ensure that the addresses of different objects of the same type are always different, it is required that the size of any object or member subobject be at least 1, even if the type is an empty class type.

    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 Empty base class optimization

        Although it is stipulated that the size of the empty class type is 1, when it is used as a base class subobject, it is sometimes not subject to this restriction, and can be completely optimized away from the object layout

//为保证同一类型的不同对象地址始终有别,要求任何对象或成员子对象的大小至少为 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

        If the type of the first non-static data member is the same as or derived from an empty base class, the empty base class optimization is disabled, since two base class subobjects of the same type are required to be in the object representation of the final derived type Must have a different address.

//为保证同一类型的不同对象地址始终有别,要求任何对象或成员子对象的大小至少为 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]] optimize empty class

        If empty member subobjects use the attribute [[no_unique_address]], they are allowed to be optimized away like empty base classes. Taking the address of such a member yields the address that may be equal to some other member of the same object.

//为保证同一类型的不同对象地址始终有别,要求任何对象或成员子对象的大小至少为 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

        And the [[no_unique_address]] optimization is essentially achieved by sharing addresses with other established members:

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

Three, explicit specifier

        3.1 explicit syntax form

        The explicit specifier can only appear in the declaration specifier sequence of the constructor or conversion function (since C++11) within the class definition, the explicit syntax form:

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

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

        A (until C++11) constructor that is declared without the function specifier explicit and has a single parameter with no default value is called a converting constructor.

        3.2 Conversion constructor

        Unlike explicit constructors, which are only considered in direct initialization (including explicit conversions like static_cast), converting constructors are also considered in copy initialization as part of user-defined conversion sequences. The common saying is that a converting constructor specifies an implicit conversion from its argument type (if any) to its class type. Note that non-explicit user-defined conversion functions also specify an implicit conversion. Implicitly declared and user-defined non-explicit copy and move constructors are also converting constructors.

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

        Both constructors (except copy or move) and user-defined conversion functions can be function templates; the meaning of adding explicit is unchanged.

//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 explicit usage constraints

         The explicit keyword has two main uses:

  •  Specifies that a constructor or conversion function (since C++11) is explicit, i.e. it cannot be used for implicit conversions and copy-initialization.
  •  Can be used with constant expressions. When the constant expression is true, it is an explicit conversion (since 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 explicit and constant expressions

        Starting from the C++20 standard, the explicit keyword can be used together with expression composition, and it is determined whether an explicit construction is required based on the return value of the expression.

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

        The explicit keyword can be used with the constant expression constexpr. However, if the constant expression evaluates to true, then the constructor is explicit.

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

Four, constexpr specifier

        4.1 constexpr specifier and const

        Since C++11, the constexpr specifier is provided, which specifies that the value of a variable or function can appear in a constant expression. The constexpr specifier declares that a function or variable can evaluate to a value at compile time. These variables and functions (given appropriate function arguments) can be used where compile-time constant expressions are required.

// 输出要求编译时常量的函数,用于测试
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

        At first glance, it is no different from const, but in the C++11 standard, in order to solve the double semantic problem of the const keyword, the semantics of const representing "read-only" are retained, and the semantics of "constant" are assigned to the newly added The constexpr keyword. Therefore, it is recommended to separate the functions of const and constexpr, that is, all scenarios expressing "read-only" semantics use const, and scenarios expressing "constant" semantics use 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)'

        In the above code, because the return value of the sqr2() function is only decorated with const, but not decorated with a more explicit constexpr, it cannot be used to initialize the array container (only constants can initialize the array container).

        4.2 constexpr usage requirements

        Variables defined by constexpr must be initialized immediately, and the full expression of its initialization including all implicit conversions, constructor calls, etc. must be a constant expression:

  • Its type must be a literal type (LiteralType).
  • It must be initialized immediately.
  • The full expression of its initialization including all implicit conversions, constructor calls, etc. must be a constant expression
  • it must have constant destructor
// 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

        A function defined by constexpr needs to meet the following conditions:

[1] It must be non-virtual, non-virtual constants cannot be reached, (before C++20)

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

[2] It must not be a coroutine, and the coroutine is usually unreachable by constants, (since 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] Its return type (if present) must be a literal type (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] Each of its parameters must be a literal type (LiteralType)
[5] For constructors and destructors (since C++20), the class must have no virtual base class

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

[6] There exists at least one set of argument values ​​such that an invocation of the function is an evaluated subexpression of a core constant expression (sufficient for a constructor for a constant initializer) (since C++14). It is not required to diagnose whether this is violated.

[7] Its function body must not be a function try block (before C++20)

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

[8] The function body must be discarded or preset, or contain only the following content (before C++14): 1) empty statement (semicolon only); 2) static_assert statement; 3) typedef that does not define a class or enumeration declaration and alias declaration; 4) using declaration; 5) using directive; 6) exactly one return statement, when the function is not a constructor.
◦The function body must not contain (since C++14, before C++203): 1) goto statement; 2) statements with labels other than case and default; 3) (before C++20) try block; 4) (before C++20) asm statement; 5) (before C++20) variable definition without initialization; 6) variable definition of non-literal type; 7) definition of static or thread storage period variable ;8) (A function body that is =default; or =delete; does not contain any of the above.)

[9] The constexpr constructor whose function body is not =delete; must meet the following additional requirements: 1) For the constructor of a class or structure, each subobject and each non-variant non-static data member must be initialized. If the class is a union-style class, for each non-empty anonymous union member, exactly one variant member must be initialized (before C++20); 2) For the constructor of a non-empty union, there must be exactly one Non-static data members are initialized (before C++20); 3) Every constructor selected to initialize non-static members and base classes must be a constexpr constructor.
[10] The destructor cannot be constexpr, but the ordinary destructor can be implicitly called in the constant expression. (until C++20)

[11] The constexpr destructor of the function body not=delete; must meet the following additional requirements: ◦Every destructor used to destroy non-static data members and base classes must be a constexpr destructor. (since C++20)

        4.3 The use of constexpr

        Please remember that as long as there is a constexpr, it must be reachable by a constant expression, that is, the constant result can be calculated at compile time. If the variable is modified, the variable is constant and reachable. If the function is modified, it will return and the formal parameter constant can be reached. , and the function body can be determined.

#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

       The introduction of this article is over!

        Thank you readers for patiently reading to the end. My articles are very long and the knowledge points are very detailed. There may be omissions and mistakes. If there are any inadequacies, please point them out. If the content touches you, please like and follow it to avoid getting lost.

5. Source code supplement

        Compilation instructions: g++ main.cpp test*.cpp -o test.exe -std=c++20 (or 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
            };
        };
    }
};

Guess you like

Origin blog.csdn.net/py8105/article/details/129685524