你能举一个例子,static_assert(...)
('C++11') 可以优雅地解决手头的问题吗?
我熟悉运行时assert(...)
。我什么时候应该static_assert(...)
比常规更喜欢assert(...)
?
另外,在boost
有一个东西叫做BOOST_STATIC_ASSERT
,它和 一样static_assert(...)
吗?
你能举一个例子,static_assert(...)
('C++11') 可以优雅地解决手头的问题吗?
我熟悉运行时assert(...)
。我什么时候应该static_assert(...)
比常规更喜欢assert(...)
?
另外,在boost
有一个东西叫做BOOST_STATIC_ASSERT
,它和 一样static_assert(...)
吗?
静态断言用于在编译时进行断言。当静态断言失败时,程序根本无法编译。这在不同的情况下很有用,例如,如果您通过代码实现某些功能,该代码严重依赖于unsigned int
恰好具有 32 位的对象。你可以像这样放置一个静态断言
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
.
从我的头顶上...
#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
.
@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:错误:静态断言失败:“Foo::bar 太小 :(”
我用它来确保我对编译器行为、头文件、库甚至我自己的代码的假设是正确的。例如,在这里我验证结构是否已正确打包到预期的大小。
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
uint64_t BasicFile::seek(int64_t offset, enum Origin origin)
{
BOOST_STATIC_ASSERT(SEEK_SET == Origin::SET);
您应该更喜欢static_assert
在assert
编译时而不是在运行时定义行为,例如我上面给出的示例。不是这种情况的示例包括参数和返回代码检查。
BOOST_STATIC_ASSERT
是一个 pre-C++0x 宏,如果条件不满足则生成非法代码。意图是相同的,尽管static_assert
是标准化的并且可以提供更好的编译器诊断。
BOOST_STATIC_ASSERT
是功能的跨平台包装器static_assert
。
目前我正在使用 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 ... */
};
如果不满足上述任何条件,这将导致编译时错误。
一种用途static_assert
可能是确保结构(即与外部世界的接口,例如网络或文件)完全符合您的预期大小。这将捕获有人从结构中添加或修改成员而没有意识到后果的情况。会static_assert
捡起它并提醒用户。
在没有概念的情况下,可以使用static_assert
简单且可读的编译时类型检查,例如,在模板中:
template <class T>
void MyFunc(T value)
{
static_assert(std::is_base_of<MyBase, T>::value,
"T must be derived from MyBase");
// ...
}
这并没有直接回答最初的问题,而是对如何在 C++11 之前强制执行这些编译时检查进行了有趣的研究。
Andrei Alexanderscu 撰写的Modern C++ Design第 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_assert()
STATIC_CHECK(0, COMPILATION_FAILED);
static_assert(0, "compilation failed");
可static_assert
用于禁止以delete
这种方式使用关键字:
#define delete static_assert(0, "The keyword \"delete\" is forbidden.");
如果每个现代 C++ 开发人员都想使用保守的垃圾收集器,他或她可能希望通过仅使用重载operator new的class es 和struct s来调用在保守垃圾收集器的保守堆上分配内存的函数可以通过在函数开头调用一些执行此操作的函数来初始化和实例化。main
例如,每个想要使用 Boehm-Demers-Weiser 保守垃圾收集器的现代 C++ 开发人员都会在main
函数的开头编写:
GC_init();
并以这种class
方式struct
重载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
更好,应该更受欢迎。