C++ constexpr处理

The constexpr specifier declares that a function or variable can be evaluated at compile-time or runtime.

If evaluated at compile-time, the expression itself does not execute any source code and only the result of its compile-time evaluation is used in the executable. A constexpr function implies inline so it might be optimized out.

这里有一个小例子

constexpr bool use_degree = false;
constexpr double right_angle = use_degree ? 90.0 : 1.5708;

因为 use_degree 在编译时已知,且 right_angle 的条件表达式仅使用此值作为参数,所以 right_angle 可以在编译时完全计算出来。然后代码等同于

constexpr bool use_degree = false;
constexpr double right_angle = 1.5708;

条件表达式在编译时进行了评估。由于在执行期间没有其他条件需要覆盖,所以不再需要覆盖表达式 "use_degree ? 90.0 : 1.5708" 的可能性。

对于 constexpr 变量,情况很简单:它们只评估一次,且仅在编译时。对于 constexpr 函数来说,情况要复杂一些:它们可能从代码的不同部分多次调用,传递非const参数,或者可能是公共API的一部分。在这些情况下,覆盖它们的执行对于某些应用程序可能是至关重要的。

以下是一个使用三个 constexpr 函数计算数字阶乘的例子。前两个从 main() 调用,第三个未使用。

#include "output.hpp"

constexpr int fac(int n) {
    if (n < 2)
        return 1;
    else
        return n * fac(n-1);
}

constexpr int fac2(int n) {
    if (n < 2)
        return 1;
    else
        return n * fac2(n-1);
}

constexpr int fac3(int n) {
    return n < 2 ? 1 : n * fac3(n - 1);
}

int main() {
    int x = 1;
    constexpr int y = 4;
    constexpr int f = fac(y);
    int g = fac2(x);
    output_int(f);
    output_int(g);
    return 0;
}

CoverageScanner 有三个不同的选项来处理 constexpr 函数

  • --cs-constexpr=ignoreconstexpr 函数在代码覆盖率分析中根本不予考虑。这对于使用C++17之前的语言标准构建的应用程序是必要的。
  • --cs-constexpr=full:在度量代码覆盖率时,考虑所有 constexpr 函数,因此未被使用的函数将被标记为红色。
  • --cs-constexpr=runtime:只有那些在运行时执行的功能才会跟踪覆盖率分析。这意味着未使用的 constexpr 函数根本不计入整体覆盖率,并且不会被标记为红色。

让我们先看看最后一个选项, --cs-constexpr=runtime。使用这个选项在上面的例子中,我们应该能看到下面的图像所示的 MC/DC 覆盖情况。

facfac2 都从 main 中调用,但 fac 的评估可以在编译时完成,而调用 fac2 则强制函数在运行时执行,这可以通过 Coco 来测量。使用 --cs-constexpr=runtimefacfac3 都被 Coco 的覆盖测量忽略。

CoverageScanner 在测试执行时会发现需要覆盖的内容。通过这种机制,它忽略了没有生成代码的 constexpr 函数。这对于应用程序测试来说是个好行为,但对于 API 测试可能会出现问题。

API 测试的问题在于,开发者不知道编译器是否决定在编译时还是在目标运行时计算函数的结果——这取决于代码的使用方式。因此,我们添加了使用开关 --cs-constexpr=full 来测量所有 constexpr 函数覆盖情况的可能性。然后,要通过完全覆盖所有测试用例,必须编写专用的单元测试,以强制它们在运行时调用。这做起来很简单:在大多数情况下,只需将非常量变量作为参数传递给 constexpr 函数即可。

正如上面所展示的,使用 --cs-constexpr=fullfacfac3 现在标记为红色,表明需要覆盖。

在两个截图中,我们都看到 fac2() 的执行只部分覆盖,其中有一行仍然需要额外的测试来覆盖。

以下测试作为一个例子

void test_fact0()
{
    CHECK( fac(4) == 24 );
    CHECK( fac2(5) == 125 );
}

由于这些函数的参数是常量表达式,因此这些 constexpr 调用将在编译时进行评估。Coco 将无法测量它们的覆盖情况,因此 test_fact0 不会为我们的测试的整体覆盖做出贡献。

为了确保 constexpr 函数在运行时进行评估,我们需要将非 const 变量传递给它们。

void test_fact1()
{
    int n = 4;
    CHECK( fac(n) == 24 );
    CHECK( fac2(n) == 24 );
    CHECK( fac3(n) == 24 );
}

运行 test_fact1() 应该导致所有三个函数的完全覆盖。

constexpr 函数进行仪器化需要最小的 C++ 标准。以下是一个列表

constexpr 支持覆盖方法所需的 C++ 标准
--cs-constexpr=ignore所有所有
--cs-constexpr=full语句 块、决策和条件C++17
--cs-constexpr=fullMCC 和 MC/DCC++20
--cs-constexpr=runtime所有C++20

Coco v7.2.0©2024 年芬兰 Qt 公司有限公司
Qt 及相关标志是芬兰 Qt 公司以及全球其他国家的商标。所有其他商标均为各自所有者的财产。