Static_assert 是做什么的,您将用它来做什么?

你能举个例子说明 static_assert(...)(‘ C + + 11’)能够优雅地解决手头的问题吗?

我熟悉运行时 assert(...)。什么时候我应该更喜欢 static_assert(...)超过正常的 assert(...)

还有,在 boost中有一个叫做 BOOST_STATIC_ASSERT的东西,它和 static_assert(...)是一样的吗?

81193 次浏览

static_assert的一个用途可能是确保结构(即与外部世界的接口,例如网络或文件)的大小正是您所期望的。这将捕获某人从结构中添加或修改成员而没有意识到后果的情况。static_assert会接收并提醒用户。

我第一时间想到的是..。

#include "SomeLibrary.h"


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


class UsingSomeLibrary {
// ...
};

假设 SomeLibrary::Version被声明为静态 const,而不是 #defined (正如在 C + + 库中所期望的那样)。

与实际编译 SomeLibrary和您的代码、链接所有内容、仅运行可执行文件 那么来发现您花了30分钟编译一个不兼容的 SomeLibrary版本形成对比。

@ Arak,回应你的评论: 是的,你可以让 static_assert坐在任何地方,看起来:

class Foo
{
public:
static const int bar = 3;
};


static_assert(Foo::bar > 4, "Foo::bar is too small :(");


int main()
{
return Foo::bar;
}
$ g++ --std=c++0x a.cpp
a.cpp:7: error: static assertion failed: "Foo::bar is too small :("

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

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

在一个包装 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 + + 0 x 宏,如果条件不满足,它会生成非法代码。意图是相同的,虽然 static_assert是标准化的,并可能提供更好的编译器诊断。

静态断言用于在编译时进行断言。当静态断言失败时,程序就不会编译。这在不同的情况下非常有用,例如,如果您通过严重依赖具有正好32位的 unsigned int对象的代码来实现某些功能。您可以像这样放置静态断言

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

BOOST_STATIC_ASSERT是用于 static_assert功能的跨平台包装器。

目前,我正在使用 static _ asserts,以便在类上强制执行“概念”。

例如:

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 ... */
};

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

这并没有直接回答最初的问题,但是对于如何在 C + + 11之前执行这些编译时间检查做了一个有趣的研究。

Andrei Alexanderscu 在 现代 C + + 设计的第2章(第2.1节)中实现了这样的编译时断言思想

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


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

比较宏 STATIC _ CHECK ()和 STATIC _ asser()

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

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

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


// ...
}

static_assert可以用来禁止以这种方式使用 delete关键字:

#define delete static_assert(0, "The keyword \"delete\" is forbidden.");

如果每个现代 C + + 开发人员想要使用保守的垃圾收集器,只使用 同学们结构来调用在保守垃圾收集器的保守堆上分配内存的函数,这个函数可以通过调用在 main函数开始时执行此操作的函数来初始化和实例化。

例如,每个想要使用 Boehm-Demers-Weiser 保守垃圾收集器的现代 c + + 开发人员都会在 abc0函数的开头写:

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是更好的,应该更喜欢总是。