debugging static_assert的作用是什么?您将使用它做什么?

xmq68pz9  于 2022-12-13  发布在  其他
关注(0)|答案(9)|浏览(227)

你能给予一个static_assert(...)('C ++11')能优雅地解决手头问题的例子吗?
我对运行时assert(...)很熟悉。什么时候我应该更喜欢static_assert(...)而不是常规的assert(...)
另外,在boost中有一个叫做BOOST_STATIC_ASSERT的东西,它和static_assert(...)是一样的吗?

gmxoilav

gmxoilav1#

静态Assert用于在编译时进行Assert。当静态Assert失败时,程序就不能编译。这在不同的情况下很有用,例如,如果你用代码实现某些功能,而这些代码依赖于unsigned int对象,而unsigned int对象正好有32位。你可以这样放置一个静态Assert

static_assert(sizeof(unsigned int) * CHAR_BIT == 32);

在另一个平台上,如果unsigned int类型的大小不同,则编译将失败,从而引起开发人员对代码中有问题的部分的注意,并建议他们重新实现或重新检查它。
再举一个例子,您可能希望将某个整数值作为void *指针传递给某个函数(这是一个技巧,但有时很有用),并且您希望确保该整数值适合该指针

int i;

static_assert(sizeof(void *) >= sizeof i);
foo((void *) i);

您可能需要对char类型进行签名

static_assert(CHAR_MIN < 0);

或者带有负值的整数除法向零舍入

static_assert(-5 / 2 == -2);

如此等等。
运行时Assert在很多情况下可以用来代替静态Assert,但是运行时Assert只在运行时工作,并且只有当控制权传递给Assert时才起作用。因此,失败的运行时Assert可能处于休眠状态,在很长一段时间内都不会被检测到。
当然,静态Assert中的表达式必须是编译时常量,不能是运行时值,对于运行时值,你没有其他选择,只能使用普通的assert

uelo1irk

uelo1irk2#

我突然想到...

#include "SomeLibrary.h"

static_assert(SomeLibrary::Version > 2, 
         "Old versions of SomeLibrary are missing the foo functionality.  Cannot proceed!");

class UsingSomeLibrary {
   // ...
};

假设SomeLibrary::Version被声明为静态常量,而不是#define d(如C++库中所期望的)。
与必须实际编译SomeLibrary和您的代码,链接所有内容,然后运行可执行文件相比,* 然后 * 才发现您花了30分钟编译了一个不兼容的SomeLibrary版本。
阿拉克,回应您的评论:是的,您可以将static_assert放在任何位置,从外观上看:
第一次

lmvvr0a8

lmvvr0a83#

我用它来确保我对编译器行为、头文件、库甚至我自己的代码的假设是正确的。例如,我在这里验证结构体是否被正确地打包到了预期的大小。

struct LogicalBlockAddress
{
#pragma pack(push, 1)
    Uint32 logicalBlockNumber;
    Uint16 partitionReferenceNumber;
#pragma pack(pop)
};
BOOST_STATIC_ASSERT(sizeof(LogicalBlockAddress) == 6);

在 Package stdio.hfseek()的类中,我使用了一些enum Origin的快捷方式,并检查这些快捷方式是否与stdio.h定义的常量一致

uint64_t BasicFile::seek(int64_t offset, enum Origin origin)
{
    BOOST_STATIC_ASSERT(SEEK_SET == Origin::SET);

当行为是在编译时而不是在运行时定义的时候,你应该更喜欢static_assert而不是assert,就像我上面给出的例子一样。
BOOST_STATIC_ASSERT是一个C ++0x之前的宏,如果条件不满足,它会生成非法代码。尽管static_assert是标准化的,可以提供更好的编译器诊断,但目的是相同的。

eagi6jfj

eagi6jfj4#

BOOST_STATIC_ASSERTstatic_assert功能的跨平台 Package 器。
目前我正在使用static_assert,以便在类上强制执行“概念”。
例如:

template <typename T, typename U>
struct Type
{
  BOOST_STATIC_ASSERT(boost::is_base_of<T, Interface>::value);
  BOOST_STATIC_ASSERT(std::numeric_limits<U>::is_integer);
  /* ... more code ... */
};

如果不满足上述任何条件,这将导致编译时错误。

cgh8pdjw

cgh8pdjw5#

static_assert的一个用途可能是确保一个结构(即与外界的接口,如网络或文件)的大小正好是你所期望的。这将捕捉到有人在没有意识到后果的情况下从结构中添加或修改成员的情况。static_assert将拾取它并警告用户。

ef1yzkbh

ef1yzkbh6#

在没有概念的情况下,可以使用static_assert进行简单可读的编译时类型检查,例如,在模板中:

template <class T>
void MyFunc(T value)
{
static_assert(std::is_base_of<MyBase, T>::value, 
              "T must be derived from MyBase");

// ...
}
az31mfrm

az31mfrm7#

这并没有直接回答最初的问题,但是对如何在C++11之前强制执行这些编译时检查进行了有趣的研究。
Andrei Alexanderssu的Modern C++ Design的第2章(2.1节)实现了编译时Assert的思想,如下所示

template<int> struct CompileTimeError;
template<> struct CompileTimeError<true> {};

#define STATIC_CHECK(expr, msg) \
{ CompileTimeError<((expr) != 0)> ERROR_##msg; (void)ERROR_##msg; }

比较宏STATIC_CHECK()和static_assert()

STATIC_CHECK(0, COMPILATION_FAILED);
static_assert(0, "compilation failed");
zkure5ic

zkure5ic8#

要添加到所有其他答案上,它在使用非类型模板参数时也很有用。
请考虑以下示例。
假设你想定义一个函数,它的特定功能可以在编译时确定,比如下面的一个平凡函数,它返回一个在编译时确定的范围内的随机整数。但是,你想检查范围内的最小值是否小于最大值。
如果没有static_assert,您可以执行以下操作:

#include <iostream>
#include <random>

template <int min, int max>
int get_number() {
    if constexpr (min >= max) {
        throw std::invalid_argument("Min. val. must be less than max. val.\n");
    }
    srand(time(nullptr));
    static std::uniform_int_distribution<int> dist{min, max};
    std::mt19937 mt{(unsigned int) rand()};
    return dist(mt);
}

如果是min < max,则一切正常,if constexpr分支在编译时被拒绝。然而,如果是min >= max,程序仍然可以编译,但现在你有一个函数,当它被调用时,将100%地抛出异常。因此,在后一种情况下,即使“错误”(min大于或等于max)在编译时出现,也只能在运行时发现。
这就是static_assert的用武之地。
因为static_assert是在编译时计算的,所以如果它测试的布尔常量表达式被计算为false,则会生成编译时错误,程序将无法编译。
因此,上述功能可以改进为:

#include <iostream>
#include <random>

template <int min, int max>
int get_number() {
    static_assert(min < max, "Min. value must be less than max. value.\n");
    srand(time(nullptr));
    static std::uniform_int_distribution<int> dist{min, max};
    std::mt19937 mt{(unsigned int) rand()};
    return dist(mt);
}

现在,如果用等于或大于maxmin值示例化函数模板,那么static_assert将计算其布尔常量表达式为false,并将抛出编译时错误,从而立即警告您该错误,而不会在运行时出现异常。
(Note:上述方法只是一个示例,不应用于生成随机数,因为通过rand()传递给std::mt19937构造函数的种子值是相同的,因此快速连续地重复调用该函数将生成相同的数字。(由于time(nullptr)返回相同的值)-而且,由std::uniform_int_distribution生成的值的范围实际上是闭合区间,这样就可以将相同的值传递给其构造函数,以获得上界和下界(尽管不会有任何点))

hfsqlsce

hfsqlsce9#

static_assert可用于禁止使用delete关键字,方法如下:
#define delete static_assert(0, "The keyword \"delete\" is forbidden.");
如果每个现代C开发人员都希望使用保守的垃圾收集器(仅使用重载operator newclassstruct),那么他/她可能都希望这样做所述保守垃圾收集器可以通过调用在main的开始处执行此操作的某个函数来初始化和示例化得双曲余切值.
例如,每一个想要使用Boehm-Demers-Weiser保守垃圾收集器的现代C
开发人员都会在main函数的开头写上:
GC_init();
在每个classstruct中,以这种方式重载operator new

void* operator new(size_t size)
{
     return GC_malloc(size);
}

现在不再需要operator delete,因为Boehm-Demers-Weiser保守垃圾收集器负责在不再需要时释放和解除分配每个内存块,开发人员希望禁止delete关键字。
一种方法是通过以下方式重载delete operator

void operator delete(void* ptr)
{
    assert(0);
}

但是不建议这样做,因为现代的C++开发人员会知道他/她在运行时错误地调用了delete operator,但是最好在编译时尽快知道这一点。
因此,在我看来,这种情况的最佳解决方案是使用static_assert,如本答案开头所示。
当然,这也可以用BOOST_STATIC_ASSERT来实现,但我认为static_assert更好,应该总是首选。

相关问题