Apa yang dilakukan static_assert, dan untuk apa Anda akan menggunakannya?

117

Bisakah Anda memberi contoh di mana static_assert(...)('C ++ 11') akan menyelesaikan masalah di tangan dengan elegan?

Saya akrab dengan run-time assert(...). Kapan saya harus memilih static_assert(...)daripada biasa assert(...)?

Juga, di dalam boostada yang disebut BOOST_STATIC_ASSERT, apakah sama dengan static_assert(...)?

AraK
sumber
LIHAT JUGA: 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] untuk opsi lainnya. _MSG sangat bagus setelah Anda mengetahui cara menggunakannya.
KitsuneYMG

Jawaban:

82

Dari atas kepala saya...

#include "SomeLibrary.h"

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

class UsingSomeLibrary {
   // ...
};

Dengan asumsi itu SomeLibrary::Versiondideklarasikan sebagai konstanta statis, bukan sebagai #defined (seperti yang diharapkan dalam pustaka C ++).

Berbeda dengan harus benar-benar mengkompilasi SomeLibrarydan kode Anda, hubungan segalanya, dan menjalankan eksekusi hanya kemudian untuk mengetahui bahwa Anda menghabiskan 30 menit kompilasi versi tidak kompatibel SomeLibrary.

@Arak, menanggapi komentar Anda: ya, Anda bisa static_assertduduk di mana saja, dari tampilan itu:

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: error: pernyataan statis gagal: "Foo :: bar terlalu kecil :("
Mark Rushakoff
sumber
1
Saya sedikit bingung, dapatkah Anda memasukkan static_assertkonteks non-eksekusi? Tampaknya contoh yang sangat bagus :)
AraK
3
Ya, statemen statik sebagaimana mereka berdiri biasanya diimplementasikan sebagai pembuatan objek yang hanya ditentukan jika predikatnya benar. Ini hanya akan membuat global.
GManNickG
Saya tidak yakin ini memenuhi syarat untuk menjawab pertanyaan asli secara keseluruhan, tetapi demonstrasi yang bagus
Matt Joiner
2
Jawaban ini tidak memberikan detail apa pun tentang apa perbedaan antara assert dari <cassert> dan static_assert
bitek
11
@monocoder: Lihat paragraf yang dimulai dengan "Kontras dengan ...". Singkatnya: assert memeriksa kondisinya saat runtime, dan static_assert memeriksa kondisinya saat kompilasi. Jadi, jika kondisi yang Anda tegaskan diketahui pada waktu kompilasi, gunakan static_assert. Jika kondisi tidak akan diketahui sampai program berjalan, gunakan assert.
Mike DeSimone
131

Penegasan statis digunakan untuk membuat pernyataan pada waktu kompilasi. Ketika pernyataan statis gagal, program tidak dapat dikompilasi. Ini berguna dalam situasi yang berbeda, seperti, misalnya, jika Anda menerapkan beberapa fungsionalitas dengan kode yang sangat bergantung pada unsigned intobjek yang memiliki persis 32 bit. Anda dapat menempatkan pernyataan statis seperti ini

static_assert(sizeof(unsigned int) * CHAR_BIT == 32);

dalam kode Anda. Di platform lain, dengan jenis ukuran yang berbeda unsigned int, kompilasi akan gagal, sehingga menarik perhatian pengembang ke bagian kode yang bermasalah dan menyarankan mereka untuk menerapkan ulang atau memeriksanya kembali.

Untuk contoh lain, Anda mungkin ingin meneruskan beberapa nilai integral sebagai void *pointer ke suatu fungsi (hack, tetapi kadang berguna) dan Anda ingin memastikan bahwa nilai integral akan sesuai dengan pointer

int i;

static_assert(sizeof(void *) >= sizeof i);
foo((void *) i);

Anda mungkin ingin aset charjenis itu ditandatangani

static_assert(CHAR_MIN < 0);

atau pembagian integral dengan nilai negatif membulatkan ke arah nol

static_assert(-5 / 2 == -2);

Dan seterusnya.

Dalam banyak kasus, pernyataan run-time dapat digunakan sebagai pengganti pernyataan statis, tetapi pernyataan run-time hanya berfungsi pada waktu proses dan hanya jika kontrol melewati pernyataan. Karena alasan ini, pernyataan run-time yang gagal mungkin tidak aktif, tidak terdeteksi untuk waktu yang lama.

Tentu saja, ekspresi dalam pernyataan statis harus berupa konstanta waktu kompilasi. Ini tidak bisa menjadi nilai run-time. Untuk nilai run-time Anda tidak punya pilihan lain selain menggunakan yang biasa assert.

Semut
sumber
3
Bukankah static_assert DIPERLUKAN untuk memiliki string literal sebagai parameter kedua?
Trevor Hickey
3
@Trevor Hickey: Ya, benar. Tetapi saya tidak mencoba merujuk static_assertdari C ++ 11 secara khusus. Saya di static_assertatas hanyalah beberapa implementasi abstrak dari pernyataan statis. (Saya pribadi menggunakan sesuatu seperti itu dalam kode C). Jawaban saya dimaksudkan tentang tujuan umum pernyataan statis dan perbedaannya dari pernyataan run-time.
AnT
Dalam contoh pertama, Anda mengasumsikan tidak ada bit padding dalam variabel tipe unsigned int. Ini tidak dijamin oleh standar. Variabel tipe unsigned intsecara legal dapat menempati 32 bit memori, meninggalkan 16 bit di antaranya tidak digunakan (dan dengan demikian makro UINT_MAXakan sama dengan 65535). Jadi cara Anda mendeskripsikan pernyataan statis pertama (" unsigned intobjek yang memiliki persis 32 bit") menyesatkan. Untuk mencocokkan keterangan Anda, pernyataan ini harus dimasukkan juga: static_assert(UINT_MAX >= 0xFFFFFFFFu).
RalphS
@TrevorHickey tidak lagi (C ++ 17)
luizfls
13

Saya menggunakannya untuk memastikan asumsi saya tentang perilaku compiler, header, libs, dan bahkan kode saya sendiri sudah benar. Misalnya di sini saya memverifikasi bahwa struct telah dikemas dengan benar ke ukuran yang diharapkan.

struct LogicalBlockAddress
{
#pragma pack(push, 1)
    Uint32 logicalBlockNumber;
    Uint16 partitionReferenceNumber;
#pragma pack(pop)
};
BOOST_STATIC_ASSERT(sizeof(LogicalBlockAddress) == 6);

Dalam pembungkus kelas stdio.h's fseek(), saya telah mengambil beberapa cara pintas dengan enum Origindan cek bahwa mereka pintas sejajar dengan konstanta yang didefinisikan olehstdio.h

uint64_t BasicFile::seek(int64_t offset, enum Origin origin)
{
    BOOST_STATIC_ASSERT(SEEK_SET == Origin::SET);

Anda harus memilih static_assertlebih assertsaat perilaku didefinisikan pada waktu kompilasi, dan tidak pada saat runtime, seperti contoh yang saya berikan di atas. Contoh di mana hal ini tidak terjadi akan mencakup pemeriksaan parameter dan kode kembali.

BOOST_STATIC_ASSERTadalah makro pra-C ++ 0x yang menghasilkan kode ilegal jika kondisinya tidak terpenuhi. Tujuannya sama, meskipun static_assertsudah terstandarisasi dan dapat memberikan diagnostik compiler yang lebih baik.

Matt Joiner
sumber
9

BOOST_STATIC_ASSERTadalah pembungkus lintas platform untuk static_assertfungsionalitas.

Saat ini saya menggunakan static_assert untuk menerapkan "Konsep" di kelas.

contoh:

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

Ini akan menyebabkan kesalahan waktu kompilasi jika salah satu kondisi di atas tidak terpenuhi.

nurettin
sumber
3
Sekarang C ++ 11 sudah keluar (dan telah keluar untuk sementara waktu), static_assert harus didukung oleh versi yang lebih baru dari semua kompiler utama. Bagi kita yang tidak sabar menunggu C ++ 14 (yang semoga berisi batasan template), ini adalah aplikasi static_assert yang sangat berguna.
Collin
7

Salah satu kegunaannya static_assertmungkin untuk memastikan bahwa struktur (yang merupakan antarmuka dengan dunia luar, seperti jaringan atau file) persis dengan ukuran yang Anda harapkan. Ini akan menangkap kasus di mana seseorang menambah atau memodifikasi anggota dari struktur tanpa menyadari konsekuensinya. Itu static_assertakan mengambilnya dan memperingatkan pengguna.

Greg Hewgill
sumber
3

Jika tidak ada konsep yang dapat digunakan static_assertuntuk pemeriksaan jenis waktu kompilasi yang sederhana dan dapat dibaca, misalnya, dalam template:

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

// ...
}
vladon
sumber
2

Ini tidak secara langsung menjawab pertanyaan asli, tetapi membuat studi yang menarik tentang bagaimana menerapkan pemeriksaan waktu kompilasi ini sebelum C ++ 11.

Bab 2 (Bagian 2.1) Desain C ++ Modern oleh Andrei Alexanderscu mengimplementasikan gagasan pernyataan waktu kompilasi seperti ini

template<int> struct CompileTimeError;
template<> struct CompileTimeError<true> {};

#define STATIC_CHECK(expr, msg) \
{ CompileTimeError<((expr) != 0)> ERROR_##msg; (void)ERROR_##msg; } 

Bandingkan makro STATIC_CHECK () dan static_assert ()

STATIC_CHECK(0, COMPILATION_FAILED);
static_assert(0, "compilation failed");
jalur malam
sumber
-2

The static_assertdapat digunakan untuk melarang penggunaan deletekata kunci cara ini:

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

Setiap pengembang C ++ modern mungkin ingin melakukannya jika dia ingin menggunakan pengumpul sampah konservatif dengan hanya menggunakan kelas dan struct yang membebani operator new untuk memanggil fungsi yang mengalokasikan memori pada tumpukan konservatif dari pengumpul sampah konservatif yang dapat diinisialisasi dan dibuat instance-nya dengan menjalankan beberapa fungsi yang melakukan ini di awal mainfungsi.

Misalnya, setiap pengembang C ++ modern yang ingin menggunakan kolektor sampah konservatif Boehm-Demers-Weiser akan mainmenulis di awal fungsi:

GC_init();

Dan di setiap classdan structmembebani dengan operator newcara ini:

void* operator new(size_t size)
{
     return GC_malloc(size);
}

Dan sekarang hal operator deleteitu tidak diperlukan lagi, karena pengumpul sampah konservatif Boehm-Demers-Weiser bertanggung jawab untuk membebaskan dan membatalkan alokasi setiap blok memori saat tidak diperlukan lagi, pengembang ingin melarang deletekata kunci.

Salah satu caranya adalah membebani dengan delete operatorcara ini:

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

Tetapi ini tidak disarankan, karena pengembang C ++ modern akan tahu bahwa dia salah memanggil delete operatorwaktu berjalan, tetapi ini lebih baik untuk segera mengetahuinya pada waktu kompilasi.

Jadi solusi terbaik untuk skenario ini menurut saya adalah menggunakan static_assertseperti yang ditunjukkan di awal jawaban ini.

Tentu saja ini juga bisa dilakukan BOOST_STATIC_ASSERT, tapi menurut saya itu static_assertlebih baik dan harus lebih selalu disukai.

pengguna11962338
sumber