C++ Weekly - Episode 159 脱水版: `constexpr` `virtual` Members In C++20

43 阅读2分钟

# C++ Weekly - Episode 159 脱水版: constexpr virtual` Members In C++20

C++20 中的 constexpr, virtual

在 C++17 中,virtual 函数无法被同时声明为 constexpr,

error: virtual function cannot be constexpr

但是在 C++20 中,可以。这意味着对于虚函数重载也可以实现编译期实现,从而提升运行效率。参见下述一个例子:

struct Base
{
    [[nodiscard]] constexpr virtual int get_value() const = 0;
};

struct Derived: Base
{
    [[nodiscard]] constexpr int get_value() const override {
        return 5;
    }
};

struct Derived2: Base
{
    [[nodiscard]] constexpr int get_value() const override {
        return 10;
    }
};

constexpr auto get_some_values()
{
    const Derived d1;
    const Derived d2;
    const Derived d3;
    const Derived2 d4;
    const Derived2 d5;

    const std::array<const Base *, 5> data{ &d1, &d2, &d3, &d4, &d5};

    int sum = 0;
    for (const auto *elem : data) {
        sum += elem->get_value(); 
    }

    return sum;
}


int main()
{
    constexpr auto v = get_some_values();
    return v;
}

gcc std=c++20 -O3 编译条件下, get_some_values() 直接输出了结果:

main:
        mov     eax, 35
        ret

若使用非 constexpr 表达式,让其在运行时重载,源码及汇编后代码如下:

struct Base
{
    [[nodiscard]] /* constexpr */ virtual int get_value() const = 0;
};

struct Derived: Base
{
    [[nodiscard]] /* constexpr */ int get_value() const override {
        return 5;
    }
};

struct Derived2: Base
{
    [[nodiscard]] /* constexpr */ int get_value() const override {
        return 10;
    }
};

/* constexpr */ auto get_some_values()
{
    const Derived d1;
    const Derived d2;
    const Derived d3;
    const Derived2 d4;
    const Derived2 d5;

    const std::array<const Base *, 5> data{ &d1, &d2, &d3, &d4, &d5};

    int sum = 0;
    for (const auto *elem : data) {
        sum += elem->get_value(); 
    }

    return sum;
}


int main()
{
    /* constexpr */ auto v = get_some_values();
    return v;
}
Derived::get_value() const:
        mov     eax, 5
        ret
Derived2::get_value() const:
        mov     eax, 10
        ret
get_some_values():
        push    r12
        push    rbp
        xor     ebp, ebp
        push    rbx
        sub     rsp, 96
        lea     rax, [rsp+8]
        lea     rbx, [rsp+48]
        mov     QWORD PTR [rsp+8], OFFSET FLAT:vtable for Derived+16
        movq    xmm0, rax
        lea     rax, [rsp+16]
        mov     QWORD PTR [rsp+16], OFFSET FLAT:vtable for Derived+16
        lea     r12, [rsp+88]
        mov     QWORD PTR [rsp+24], OFFSET FLAT:vtable for Derived+16
        movq    xmm1, rax
        lea     rax, [rsp+24]
        mov     QWORD PTR [rsp+32], OFFSET FLAT:vtable for Derived2+16
        punpcklqdq      xmm0, xmm1
        movaps  XMMWORD PTR [rsp+48], xmm0
        movq    xmm0, rax
        lea     rax, [rsp+32]
        mov     QWORD PTR [rsp+40], OFFSET FLAT:vtable for Derived2+16
        movq    xmm2, rax
        lea     rax, [rsp+40]
        punpcklqdq      xmm0, xmm2
        mov     QWORD PTR [rsp+80], rax
        mov     eax, OFFSET FLAT:Derived::get_value() const
        movaps  XMMWORD PTR [rsp+64], xmm0
        jmp     .L6
.L8:
        mov     rax, QWORD PTR [rbx]
        mov     rax, QWORD PTR [rax]
        mov     rax, QWORD PTR [rax]
.L6:
        mov     rdi, QWORD PTR [rbx]
        add     rbx, 8
        call    rax
        add     ebp, eax
        cmp     rbx, r12
        jne     .L8
        add     rsp, 96
        mov     eax, ebp
        pop     rbx
        pop     rbp
        pop     r12
        ret
main:
        jmp     get_some_values()
typeinfo name for Base:
        .string "4Base"
typeinfo for Base:
        .quad   vtable for __cxxabiv1::__class_type_info+16
        .quad   typeinfo name for Base
typeinfo name for Derived:
        .string "7Derived"
typeinfo for Derived:
        .quad   vtable for __cxxabiv1::__si_class_type_info+16
        .quad   typeinfo name for Derived
        .quad   typeinfo for Base
typeinfo name for Derived2:
        .string "8Derived2"
typeinfo for Derived2:
        .quad   vtable for __cxxabiv1::__si_class_type_info+16
        .quad   typeinfo name for Derived2
        .quad   typeinfo for Base
vtable for Derived:
        .quad   0
        .quad   typeinfo for Derived
        .quad   Derived::get_value() const
vtable for Derived2:
        .quad   0
        .quad   typeinfo for Derived2
        .quad   Derived2::get_value() const

结果显而易见。