你能举个例子说明 static_assert(...)(‘ C + + 11’)能够优雅地解决手头的问题吗?
static_assert(...)
我熟悉运行时 assert(...)。什么时候我应该更喜欢 static_assert(...)超过正常的 assert(...)?
assert(...)
还有,在 boost中有一个叫做 BOOST_STATIC_ASSERT的东西,它和 static_assert(...)是一样的吗?
boost
BOOST_STATIC_ASSERT
static_assert的一个用途可能是确保结构(即与外部世界的接口,例如网络或文件)的大小正是您所期望的。这将捕获某人从结构中添加或修改成员而没有意识到后果的情况。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::Version
#define
与实际编译 SomeLibrary和您的代码、链接所有内容、仅运行可执行文件 那么来发现您花了30分钟编译一个不兼容的 SomeLibrary版本形成对比。
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.h的 fseek()的类中,我使用了 enum Origin的一些快捷方式,并检查这些快捷方式是否与 stdio.h定义的常量保持一致
stdio.h
fseek()
enum Origin
uint64_t BasicFile::seek(int64_t offset, enum Origin origin) { BOOST_STATIC_ASSERT(SEEK_SET == Origin::SET);
如果行为是在编译时定义的,而不是在运行时定义的,比如上面给出的示例,那么您应该更喜欢 static_assert而不是 assert。这是 没有的一个例子,该例子将包括参数检查和返回代码检查。
assert
BOOST_STATIC_ASSERT是一个前 C + + 0 x 宏,如果条件不满足,它会生成非法代码。意图是相同的,虽然 static_assert是标准化的,并可能提供更好的编译器诊断。
静态断言用于在编译时进行断言。当静态断言失败时,程序就不会编译。这在不同的情况下非常有用,例如,如果您通过严重依赖具有正好32位的 unsigned int对象的代码来实现某些功能。您可以像这样放置静态断言
unsigned int
static_assert(sizeof(unsigned int) * CHAR_BIT == 32);
在你的代码里。在另一个平台上,使用不同大小的 unsigned int类型,编译将会失败,从而引起开发人员对代码中有问题的部分的注意,并建议他们重新实现或重新检查代码。
另一个例子是,您可能希望将某个整数值作为指向函数的 void *指针传递(这是一种技巧,但有时很有用) ,并且希望确保整数值适合于指针
void *
int i; static_assert(sizeof(void *) >= sizeof i); foo((void *) i);
您可能希望对 char类型进行签名
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关键字:
delete
#define delete static_assert(0, "The keyword \"delete\" is forbidden.");
如果每个现代 C + + 开发人员想要使用保守的垃圾收集器,只使用 同学们和 结构来调用在保守垃圾收集器的保守堆上分配内存的函数,这个函数可以通过调用在 main函数开始时执行此操作的函数来初始化和实例化。
main
例如,每个想要使用 Boehm-Demers-Weiser 保守垃圾收集器的现代 c + + 开发人员都会在 abc0函数的开头写:
GC_init();
在每个 class和 struct中,operator new都以这种方式超载:
class
struct
operator new
void* operator new(size_t size) { return GC_malloc(size); }
现在不再需要 operator delete了,因为 Boehm-Demers-Weiser 保守的垃圾收集器负责释放和释放不再需要的每个内存块,开发人员希望禁止使用 delete关键字。
operator delete
一种方法是这样使 delete operator过载:
delete operator
void operator delete(void* ptr) { assert(0); }
但是不推荐这样做,因为现代的 C + + 开发人员会知道他/她在运行时错误地调用了 delete operator,但是最好在编译时尽快知道这一点。
所以在我看来,这个场景的最佳解决方案是使用 static_assert,如本答案开头所示。
当然,这也可以做到与 BOOST_STATIC_ASSERT,但我认为,static_assert是更好的,应该更喜欢总是。