【问题标题】:Refactoring Code to assert at compile time instead of throwing an exception at runtime重构代码以在编译时断言而不是在运行时抛出异常
【发布时间】:2021-01-30 15:56:17
【问题描述】:

考虑下面的代码示例,一个简单的模板类包装器,带有基本的重载算术运算符。在此类的operator/ 中,如果检测到除以 0,我使用三元运算符抛出异常,否则,我将返回计算结果。

一些.h

#pragma once

#include <cassert>
#include <stdexcept>

template<typename T>
struct Var {
    T var;
   
    // ... other operators
 
    const auto operator/(const Var& rhs) {        
        return ((rhs.var == 0) ? throw std::exception("Division by 0") : (var / rhs.var));
    }
};

这是驱动程序:

#include <iostream>

#include "some.h"

int main() {
    try {
        Var<int> t1{ 4 };
        Var<int> t2{ 0 };
        auto t3 = t1 / t2;
        std::cout << t3 << '\n';
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    } catch (...) {
        std::cerr << "Unknown Exception\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

这将编译和构建,当我们运行它时(我使用的是 Visual Studio),它会抛出一个异常,将这条消息提供给控制台

Division by 0

C:\Users\...\source\repos\Data Structure Samples\x64\Debug\Data Structure Samples.exe (process 8756) exited with cod
e 1.
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the conso
le when debugging stops.
Press any key to close this window . . .

好的,这很简单,而且有效。


假设我想重构这段代码有两个主要目标:

  • 首先,将运算符从类中抽象出来
  • 不想抛出异常,而是宁愿在编译时断言。

这个类会变成这样:

一些.h

#pragma once

#include <cassert>
#include <stdexcept>

template<typename T>
struct Var {
    T var;
};

// ... other operators

template<typename T>
const auto operator/(const Var<T>& lhs, const Var<T>& rhs) {
    static_assert(rhs.var != 0, "Division by 0!");

    return (lhs.var / rhs.var);
}

但是,由于这些生成的错误无法编译...

1>------ Build started: Project: Data Structure Samples, Configuration: Debug x64 ------
1>main.cpp
1>c:\users\...\source\repos\data structure samples\data structure samples\datastructs.h(25): error C2131: expression did not evaluate to a constant
1>c:\users\...\source\repos\data structure samples\data structure samples\datastructs.h(25): note: failure was caused by a read of a variable outside its lifetime
1>c:\users\...\source\repos\data structure samples\data structure samples\datastructs.h(25): note: see usage of 'rhs'
1>c:\users\...\source\repos\data structure samples\data structure samples\main.cpp(15): note: see reference to function template instantiation 'const auto operator /<int>(const Var<int> &,const Var<int> &)' being compiled
1>Done building project "Data Structure Samples.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

我可以很容易地在运行时使用assert 而没有任何问题,但是,我正在努力弄清楚如何在这种情况下正确使用static_assert。我做错了什么,我在这里错过了什么?我在网上搜索了static_assert 的各种用例示例,但没有找到合适的。即使我将运算符移回类内部并尝试使用static_assert,我仍然会收到非常相似的错误消息。

【问题讨论】:

  • 你如何在编译时知道一个操作数是否为0?
  • 操作数仅在运行时才知道(例如从用户处给出)。不可能在编译时断言它们。如果你不想抛出异常,你可以返回一个 Var cotaining NaN 或类似的东西。
  • 该类是模板化的,它必须推断类型,并且在main 函数内t2 被初始化为0。这应该无法编译......我认为static_assert应该能够检测到这一点。这不像我在运行时从控制台或某些文件中获取值。
  • “常数值”通常是指编译时间常数。我认为你想要的是不可能的。您可以使用consteval,以便您的函数只能在编译时使用。但这可能不是你想要的。
  • 抱歉,C++ 不能这样工作。

标签: c++ c++17 assert throw static-assert


【解决方案1】:

在四处挖掘之后,我找到了一个可行的解决方案。断言或抛出异常都不会导致程序中止。

但是,在以后的计算中使用它时会有点担心...这将迫使我以这些术语编写其余的源代码,并且必须检查所有内容以确保它不是 @ 987654321@ 以防止任何进一步的计算提前中止而不退出应用程序。

话虽如此,这就是我所做的,这就是我的操作员现在的样子......

#include <cmath>

template<typename T>
struct Var {
    T var;
};

template<typename T>
const auto operator/(const Var<T>& lhs, const Var<T>& rhs) {
    return (rhs.var == 0) ? nan("0") : (lhs.var / rhs.var);
}

当我运行相同的程序时,nan 会打印到控制台。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-10
    相关资源
    最近更新 更多