你能举一个例子,static_assert(...) 'C++0x'
可以优雅地解决问题吗?
我熟悉运行时assert(...)
。我应该何时更喜欢static_assert(...)
而不是常规assert(...)
?
此外,在boost
中有一个名为BOOST_STATIC_ASSERT
的内容,它与static_assert(...)
相同吗?
答案 0 :(得分:116)
静态断言用于在编译时进行断言。当静态断言失败时,程序根本无法编译。这在不同情况下很有用,例如,如果您通过严格依赖于具有正好32位的unsigned int
对象的代码实现某些功能。你可以像这样放置一个静态断言
static_assert(sizeof(unsigned int) * CHAR_BIT == 32);
你的代码中的。在另一个平台上,使用不同大小的unsigned int
类型,编译将失败,从而引起开发人员对代码中有问题的部分的注意,并建议他们重新实现或重新检查它。
另一个例子,您可能希望将一些整数值作为void *
指向函数的指针(一个hack,但有时很有用),并且您希望确保整数值适合指针
int i;
static_assert(sizeof(void *) >= sizeof i);
foo((void *) i);
您可能希望资产char
类型已签名
static_assert(CHAR_MIN < 0);
或负值的积分除以零
static_assert(-5 / 2 == -2);
等等。
在许多情况下可以使用运行时断言而不是静态断言,但运行时断言仅在运行时才起作用,并且仅在控制通过断言时才起作用。因此,失败的运行时断言可能处于休眠状态,长时间未被发现。
当然,静态断言中的表达式必须是编译时常量。它不能是运行时值。对于运行时值,您没有其他选择,只能使用普通的assert
。
答案 1 :(得分:65)
脱离我的头顶......
#include "SomeLibrary.h"
static_assert(SomeLibrary::Version > 2,
"Old versions of SomeLibrary are missing the foo functionality. Cannot proceed!");
class UsingSomeLibrary {
// ...
};
假设SomeLibrary::Version
被声明为静态const,而不是#define
d(正如人们在C ++库中所期望的那样)。
与必须实际编译SomeLibrary
和您的代码,链接所有内容,然后仅运行可执行文件然后以确定您花费30分钟编译不兼容版本的{{1 }}
@Arak,回应你的评论:是的,你可以让SomeLibrary
只是坐在外面,从外观上看:
static_assert
$ g++ --std=c++0x a.cpp a.cpp:7: error: static assertion failed: "Foo::bar is too small :("
答案 2 :(得分:12)
我用它来确保我对编译器行为,头文件,库甚至我自己的代码的假设都是正确的。例如,我在此验证结构已正确打包到预期大小。
struct LogicalBlockAddress
{
#pragma pack(push, 1)
Uint32 logicalBlockNumber;
Uint16 partitionReferenceNumber;
#pragma pack(pop)
};
BOOST_STATIC_ASSERT(sizeof(LogicalBlockAddress) == 6);
在包装stdio.h
的{{1}}的类中,我使用了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
是一个前C ++ 0x宏,如果条件不满足则会生成非法代码。意图是相同的,尽管BOOST_STATIC_ASSERT
是标准化的,可能提供更好的编译器诊断。
答案 3 :(得分:9)
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 ... */
};
如果不满足上述任何条件,这将导致编译时错误。
答案 4 :(得分:5)
static_assert
的一个用途可能是确保结构(与外部世界的接口,例如网络或文件)完全符合您的预期。这将捕获某人在没有意识到后果的情况下从结构中添加或修改成员的情况。 static_assert
将提取并提醒用户。
答案 5 :(得分:3)
如果没有概念,可以使用static_assert
进行简单易读的编译时类型检查,例如,在模板中:
template <class T>
void MyFunc(T value)
{
static_assert(std::is_base_of<MyBase, T>::value,
"T must be derived from MyBase");
// ...
}
答案 6 :(得分:2)
这并没有直接回答原始问题,而是对如何在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");
答案 7 :(得分:-1)
static_assert
可用于通过以下方式禁止使用delete
关键字:
#define delete static_assert(0, "The keyword \"delete\" is forbidden.");
如果每个现代C ++开发人员想使用保守的垃圾回收器,而只使用重载的 class es和 struct ,则可能想要这样做> operator new 来调用一个在保守垃圾回收器的保守堆上分配内存的函数,该函数可以通过在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
更好,应该更优先使用。