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

คำตอบ:


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
ใช่การยืนยันแบบคงที่ในขณะที่พวกเขายืนมักจะถูกนำไปใช้ในการสร้างวัตถุที่กำหนดเฉพาะในกรณีที่เพรดิเคตเป็นจริง สิ่งนี้จะทำให้ทั่วโลก
GManNickG

ฉันไม่แน่ใจว่านี่เป็นการตอบคำถามเดิมอย่างครบถ้วน แต่การสาธิตที่ดี
Matt Joiner

2
คำตอบนี้ไม่ได้ให้รายละเอียดว่าอะไรคือความแตกต่างระหว่างassertจาก <cassert> และstatic_assert
bitek

11
@monocoder: ดูย่อหน้าที่ขึ้นต้นด้วย "Contrast with ... " กล่าวโดยย่อ: assertตรวจสอบเงื่อนไขที่รันไทม์และstatic_assertตรวจสอบเงื่อนไขในการคอมไพล์ ดังนั้นหากทราบเงื่อนไขที่คุณยืนยันในเวลาคอมไพล์ให้ใช้static_assert. หากไม่ทราบเงื่อนไขจนกว่าโปรแกรมจะทำงานให้ใช้assert.
Mike DeSimone

131

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แต่ใช้สามัญ


3
static_assert ไม่จำเป็นต้องมีสตริงลิเทอรัลเป็นพารามิเตอร์ที่สองหรือไม่?
Trevor Hickey

3
@Trevor Hickey: ใช่แล้ว แต่ฉันไม่ได้พยายามอ้างถึงstatic_assertจาก C ++ 11 โดยเฉพาะ static_assertข้างต้นของฉันเป็นเพียงการใช้การยืนยันแบบคงที่ (โดยส่วนตัวฉันใช้อะไรทำนองนั้นในรหัส C) คำตอบของฉันมีจุดมุ่งหมายเพื่อวัตถุประสงค์ทั่วไปของการยืนยันแบบคงที่และความแตกต่างจากการยืนยันรันไทม์
AnT

ในตัวอย่างแรกคุณจะสมมติว่าไม่มีบิต padding unsigned intในตัวแปรของชนิด มาตรฐานนี้ไม่รับประกัน ตัวแปรประเภทunsigned intสามารถครอบครองหน่วยความจำ 32 บิตได้อย่างถูกกฎหมายโดยปล่อยให้ 16 หน่วยความจำไม่ได้ใช้งาน (ดังนั้นมาโครUINT_MAXจะเท่ากับ65535) ดังนั้นวิธีที่คุณอธิบายการยืนยันแบบคงที่ครั้งแรก (" unsigned intอ็อบเจ็กต์ที่มี 32 บิต") จึงทำให้เข้าใจผิด เพื่อให้ตรงกับคำอธิบายของคุณ, static_assert(UINT_MAX >= 0xFFFFFFFFu)การยืนยันนี้ควรจะรวมอยู่เช่นกัน:
RalphS

@TrevorHickey ไม่อีกแล้ว (C ++ 17)
luizfls

13

ฉันใช้มันเพื่อให้แน่ใจว่าสมมติฐานของฉันเกี่ยวกับพฤติกรรมของคอมไพเลอร์ส่วนหัว libs และแม้แต่โค้ดของฉันเองนั้นถูกต้อง ตัวอย่างเช่นที่นี่ฉันตรวจสอบว่าโครงสร้างได้รับการบรรจุอย่างถูกต้องตามขนาดที่คาดไว้

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เป็นมาโคร 0x ก่อน C ++ ที่สร้างรหัสที่ไม่ถูกต้องหากเงื่อนไขไม่เป็นไปตามเงื่อนไข ความตั้งใจเหมือนกันแม้ว่าจะ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 ที่มีประโยชน์มาก
Collin

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

บทที่ 2 (ส่วนที่ 2.1) ของการออกแบบ C ++ สมัยใหม่โดย Andrei Alexanderscu ใช้แนวคิดของการยืนยันเวลาคอมไพล์เช่นนี้

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 ++ สมัยใหม่ทุกคนอาจต้องการทำเช่นนั้นหากต้องการใช้ตัวเก็บขยะแบบอนุรักษ์นิยมโดยใช้เฉพาะคลาส es และstruct s ที่โอเวอร์โหลดตัวดำเนินการใหม่เพื่อเรียกใช้ฟังก์ชันที่จัดสรรหน่วยความจำบนกองอนุรักษ์นิยมของตัวเก็บขยะแบบอนุรักษ์นิยมที่ สามารถเริ่มต้นและสร้างอินสแตนซ์ได้โดยการเรียกใช้ฟังก์ชันบางอย่างที่ทำสิ่งนี้ในตอนต้นของmainฟังก์ชัน

ตัวอย่างเช่นนักพัฒนา C ++ สมัยใหม่ทุกคนที่ต้องการใช้ตัวเก็บขยะแบบอนุรักษ์นิยมของ Boehm-Demers-Weiser จะเขียนในตอนต้นของ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ดีกว่าและควรเป็นที่ต้องการมากกว่าเสมอ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.