【问题标题】:What does static_assert do, and what would you use it for?static_assert 有什么作用,你会用它做什么?
【发布时间】:2010-12-11 11:57:52
【问题描述】:

你能举个例子,static_assert(...) ('C++11') 可以优雅地解决手头的问题吗?

我熟悉运行时assert(...)。我什么时候应该更喜欢static_assert(...) 而不是常规的assert(...)

另外,在boost中有一个叫BOOST_STATIC_ASSERT的东西,和static_assert(...)一样吗?

【问题讨论】:

标签: c++ debugging c++11 assert static-assert


【解决方案1】:

静态断言用于在编译时进行断言。当静态断言失败时,程序根本无法编译。这在不同的情况下很有用,例如,如果您通过代码实现某些功能,该代码严重依赖于具有正好 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

【讨论】:

  • Static_assert 是否需要将字符串文字作为第二个参数?
  • @Trevor Hickey:是的。但我并没有试图专门从 C++11 引用 static_assert。我上面的static_assert 只是静态断言的一些抽象实现。 (我个人在 C 代码中使用类似的东西)。我的回答是关于静态断言的一般用途及其与运行时断言的区别。
  • 在第一个示例中,您假设unsigned int 类型的变量中没有填充位。标准不保证这一点。 unsigned int 类型的变量可以合法占用 32 位内存,其中 16 位未使用(因此宏 UINT_MAX 将等于 65535)。因此,您描述第一个静态断言的方式(“unsigned int 对象 具有 正好 32 位”)具有误导性。为了符合您的描述,还应包含此断言:static_assert(UINT_MAX &gt;= 0xFFFFFFFFu)
  • @TrevorHickey 不再 (C++17)
【解决方案2】:

在我的头顶...

#include "SomeLibrary.h"

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

class UsingSomeLibrary {
   // ...
};

假设 SomeLibrary::Version 被声明为静态常量,而不是 #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 太小 :(”

【讨论】:

  • 我有点困惑,你能把static_assert 放在非执行上下文中吗?这似乎是一个很好的例子:)
  • 是的,静态断言通常被实现为创建一个仅在谓词为真时才定义的对象。这只会成为一个全球性的。
  • 我不确定这是否有资格完整地回答原始问题,但很好的演示
  • 这个答案没有提供任何关于 assert from static_assert 的区别的细节
  • @monocoder:参见以“与...对比”开头的段落。简而言之:assert 在运行时检查其条件,而 static_assert 在编译时检查其条件。因此,如果您断言的条件在编译时已知,请使用static_assert。如果在程序运行之前不知道条件,请使用assert
【解决方案3】:

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

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++0x 之前的宏,如果条件不满足,则会生成非法代码。意图是相同的,尽管 static_assert 是标准化的,并且可以提供更好的编译器诊断。

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

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

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

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

        【解决方案7】:

        这并没有直接回答最初的问题,而是对如何在 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");
        

        【讨论】:

          【解决方案8】:

          static_assert可以用来禁止delete关键字的使用:

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

          如果每个现代 C++ 开发人员都想通过仅使用重载 classes 和 structs 来使用保守的垃圾收集器,他或她可能想要这样做>operator new 调用一个函数,该函数在保守垃圾收集器的保守堆上分配内存,可以通过在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 保守垃圾收集器负责在不再需要时释放和释放每个内存块,开发人员希望禁止 @ 987654332@关键字。

          一种方法是以这种方式重载delete operator

          void operator delete(void* ptr)
          {
              assert(0);
          }
          

          但不建议这样做,因为现代 C++ 开发人员会知道他/她在运行时错误地调用了 delete operator,但最好在编译时尽快知道这一点。

          因此,我认为这种情况的最佳解决方案是使用static_assert,如本答案开头所示。

          当然,BOOST_STATIC_ASSERT 也可以做到这一点,但我认为static_assert 更好,应该更经常使用。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-09-26
            • 1970-01-01
            • 2011-04-15
            • 2010-11-08
            • 1970-01-01
            • 2012-11-17
            • 2011-01-14
            • 2011-12-02
            相关资源
            最近更新 更多