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


117

您能否举一个例子static_assert(...)(C ++ 11)轻松解决问题?

我对运行时很熟悉assert(...)。我什么时候static_assert(...)比常规更喜欢assert(...)

另外,在 boost其中有一个叫BOOST_STATIC_ASSERT,它和一样static_assert(...)吗?


另请参见:BOOST_MPL_ASSERT,BOOST_MPL_ASSERT_NOT,BOOST_MPL_ASSERT_MSG,BOOST_MPL_ASSERT_RELATION [ boost.org/doc/libs/1_40_0/libs/mpl/doc/refmanual/asserts.html]了解更多选项。一旦弄清楚如何使用_MSG尤其好。
KitsuneYMG,2009年

Answers:


82

从我的头顶上...

#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:错误:静态声明失败:“ Foo :: bar太小:(”

1
我有点困惑,您可以置于static_assert非执行环境吗?这似乎是一个很好的例子:)
AraK

3
是的,静态断言通常被实现为创建仅在谓词为true时定义的对象。这只会使全球性。
GManNickG

我不确定这是否可以完整地回答原始问题,但很好的演示
Matt Joiner 2009年

2
此答案未提供有关<cassert>中的assertstatic_assert
bitek

11
@monocoder:请参见以“与...对比”开头的段落。简而言之:assert在运行时检查其条件,而static_assert在编译时检查其条件。因此,如果在编译时知道要声明的条件,请使用static_assert。如果在程序运行之前不知道条件,请使用assert
Mike DeSimone

131

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

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);

等等。

在许多情况下,可以使用运行时断言代替静态断言,但是运行时断言仅在运行时有效,并且仅在控制权移交给断言时起作用。因此,失败的运行时断言可能处于休眠状态,长时间未检测到。

当然,静态断言中的表达式必须是编译时常量。它不能是运行时值。对于运行时值,您别无选择,只能使用normal assert


3
是否不是static_assert需要字符串文字作为第二个参数?
Trevor Hickey

3
@Trevor Hickey:是的。但是我并不是想static_assert专门从C ++ 11 引用。我的static_assert上面只是静态断言的一些抽象实现。(我个人在C代码中使用类似的东西)。我的回答是关于静态断言的一般用途及其与运行时断言的区别。
2013年

在第一个示例中,您假设type变量中没有填充位unsigned int。该标准不能保证。类型的变量unsigned int可以合法地占用32位内存,而其中16位未使用(因此宏UINT_MAX将等于65535)。因此,您描述第一个静态断言(“ 具有正好32位的unsigned int对象”)的方式具有误导性。为了符合您的描述,该断言也应包括在内:。static_assert(UINT_MAX >= 0xFFFFFFFFu)
RalphS

@TrevorHickey不再(C ++ 17)
luizfls

13

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

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_assertassert当行为是在编译时定义,而不是在运行时,如我上面给出的例子。一个例子,这是没有的情况下,将包括参数和返回代码检查。

BOOST_STATIC_ASSERT是C ++ 0x之前的版本,如果不满足条件,则生成非法代码。意图是相同的,尽管static_assert是标准化的,并且可以提供更好的编译器诊断。


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

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


3
现在C ++ 11已经发布(并且已经发布了一段时间),所有主要编译器的最新版本都应该支持static_assert。对于我们等不及C ++ 14(希望它将包含模板约束)的人来说,这是static_assert的非常有用的应用程序。
科林

7

的一种用法static_assert是确保结构(即与外界的接口,例如网络或文件)与您期望的大小完全相同。这将捕获某些情况,即有人从结构中添加或修改成员而没有意识到后果。该static_assert会捡起来,并提醒用户。


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");

// ...
}

2

这并不能直接回答原始问题,而是对如何在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_assert()

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

-2

static_assert可用于禁止使用的delete这样的关键字:

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

如果每个现代C ++开发人员想使用保守的垃圾回收器,而只想使用使操作符new重载的 es和struct来调用保守的垃圾回收器的保守堆上分配内存的函数,则他或她想使用保守的垃圾回收器可以通过调用一些在函数开头执行此操作的函数来初始化和实例化。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更好,应该更优先选择。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.