Static_assert làm gì và bạn sẽ sử dụng nó để làm gì?


117

Bạn có thể đưa ra một ví dụ trong đó static_assert(...)('C ++ 11') sẽ giải quyết vấn đề trong tay một cách dễ dàng không?

Tôi đã quen với thời gian chạy assert(...). Khi nào tôi nên thích static_assert(...)hơn bình thường assert(...)?

Ngoài ra, trong boostcó một cái gì đó được gọi là BOOST_STATIC_ASSERT, nó giống như static_assert(...)?


XEM CŨNG: 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] để có thêm tùy chọn. _MSG đặc biệt tuyệt vời khi bạn tìm ra cách sử dụng nó.
KitsuneYMG

Câu trả lời:


82

Off đỉnh đầu của tôi...

#include "SomeLibrary.h"

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

class UsingSomeLibrary {
   // ...
};

Giả sử điều đó SomeLibrary::Versionđược khai báo là một const tĩnh, thay vì là #defined (như người ta mong đợi trong thư viện C ++).

Ngược lại với việc phải thực sự biên dịch SomeLibraryvà mã của bạn, liên kết mọi thứ và chỉ chạy tệp thực thi sau đó để biết rằng bạn đã dành 30 phút để biên dịch một phiên bản không tương thích của SomeLibrary.

@Arak, để trả lời nhận xét của bạn: vâng, bạn có thể static_assertngồi ở bất cứ đâu, từ cái nhìn của nó:

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: static khẳng định không thành công: "Foo :: bar quá nhỏ :("

1
Tôi hơi bối rối, bạn có thể đặt static_asserttrong bối cảnh không thực thi được không? Nó có vẻ là một ví dụ rất hay :)
AraK

3
Vâng, các xác nhận tĩnh khi chúng đứng thường được triển khai khi tạo một đối tượng chỉ được xác định nếu vị từ là true. Điều này sẽ chỉ làm cho một toàn cầu.
GManNickG

Tôi không chắc chắn đủ tiêu chuẩn này là trả lời câu hỏi ban đầu trong đó là toàn bộ, nhưng trình diễn đẹp
Matt Joiner

2
Câu trả lời này không cung cấp bất kỳ thông tin chi tiết nào về sự khác biệt giữa khẳng định từ <cassert> và static_assert
cắnk

11
@monocoder: Xem đoạn bắt đầu bằng "Tương phản với ...". Tóm lại: khẳng định kiểm tra điều kiện của nó khi chạy, và static_assert kiểm tra điều kiện của nó khi biên dịch. Vì vậy, nếu điều kiện bạn đang xác nhận đã biết tại thời điểm biên dịch, hãy sử dụng static_assert. Nếu điều kiện không được biết cho đến khi chương trình chạy, hãy sử dụng assert.
Mike DeSimone

131

Khẳng định tĩnh được sử dụng để thực hiện xác nhận tại thời điểm biên dịch. Khi xác nhận tĩnh không thành công, chương trình chỉ đơn giản là không biên dịch. Điều này hữu ích trong các tình huống khác nhau, chẳng hạn như nếu bạn triển khai một số chức năng bằng mã phụ thuộc nghiêm trọng vào unsigned intđối tượng có chính xác 32 bit. Bạn có thể đặt một khẳng định tĩnh như thế này

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

trong mã của bạn. Trên một nền tảng khác, với unsigned intkiểu kích thước khác, quá trình biên dịch sẽ không thành công, do đó, nhà phát triển thu hút sự chú ý của nhà phát triển đến phần có vấn đề của mã và khuyên họ nên triển khai lại hoặc kiểm tra lại nó.

Ví dụ khác, bạn có thể muốn chuyển một số giá trị tích phân dưới dạng void *con trỏ đến một hàm (một cách hack, nhưng đôi khi hữu ích) và bạn muốn đảm bảo rằng giá trị tích phân sẽ phù hợp với con trỏ

int i;

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

Bạn có thể muốn tài sản mà charloại được ký

static_assert(CHAR_MIN < 0);

hoặc phép chia tích phân đó với các giá trị âm làm tròn về 0

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

Và như thế.

Xác nhận thời gian chạy trong nhiều trường hợp có thể được sử dụng thay cho xác nhận tĩnh, nhưng xác nhận thời gian chạy chỉ hoạt động tại thời gian chạy và chỉ khi quyền kiểm soát vượt qua xác nhận. Vì lý do này, xác nhận thời gian chạy không thành công có thể nằm im, không bị phát hiện trong thời gian dài.

Tất nhiên, biểu thức trong khẳng định tĩnh phải là một hằng số thời gian biên dịch. Nó không thể là một giá trị thời gian chạy. Đối với các giá trị thời gian chạy, bạn không có lựa chọn nào khác ngoài việc sử dụng giá trị thông thường assert.


3
Không phải static_assert BẮT BUỘC phải có một chuỗi là một tham số thứ hai?
Trevor Hickey

3
@Trevor Hickey: Đúng vậy. Nhưng tôi đã không cố gắng tham khảo cụ thể static_asserttừ C ++ 11. Ở static_asserttrên của tôi chỉ là một số triển khai trừu tượng của xác nhận tĩnh. (Cá nhân tôi sử dụng một cái gì đó như vậy trong mã C). Câu trả lời của tôi là về mục đích chung của các xác nhận tĩnh và sự khác biệt của chúng với các xác nhận thời gian chạy.
AnT

Trong ví dụ đầu tiên, bạn đang giả định rằng không có bit đệm trong một biến kiểu unsigned int. Điều này không được đảm bảo bởi tiêu chuẩn. Một biến kiểu unsigned intcó thể chiếm 32 bit bộ nhớ một cách hợp pháp, để lại 16 bit trong số đó không được sử dụng (và do đó macro UINT_MAXsẽ bằng 65535). Vì vậy, cách bạn mô tả xác nhận tĩnh đầu tiên (" unsigned intđối tượng chính xác 32 bit") là sai lầm. Để phù hợp với mô tả của bạn, khẳng định điều này nên được bao gồm cũng như: static_assert(UINT_MAX >= 0xFFFFFFFFu).
RalphS

@TrevorHickey không còn nữa (C ++ 17)
luizfls

13

Tôi sử dụng nó để đảm bảo các giả định của tôi về hành vi của trình biên dịch, tiêu đề, lib và thậm chí là mã của riêng tôi là đúng. Ví dụ ở đây tôi xác minh rằng cấu trúc đã được đóng gói chính xác với kích thước mong đợi.

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

Trong một gói lớp stdio.h's fseek(), tôi đã thực hiện một số phím tắt với enum Originvà kiểm tra rằng những phím tắt phù hợp với các hằng số được xác định bởistdio.h

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

Bạn nên thích static_asserthơn assertkhi hành vi được xác định tại thời điểm biên dịch chứ không phải trong thời gian chạy, chẳng hạn như các ví dụ tôi đã đưa ra ở trên. Một ví dụ không phải như vậy sẽ bao gồm kiểm tra tham số và mã trả về.

BOOST_STATIC_ASSERTlà một macro trước C ++ 0x tạo mã bất hợp pháp nếu điều kiện không được thỏa mãn. Các mục đích đều giống nhau, mặc dù static_assertđược tiêu chuẩn hóa và có thể cung cấp chẩn đoán trình biên dịch tốt hơn.


9

BOOST_STATIC_ASSERTlà một trình bao bọc nền tảng chéo cho static_assertchức năng.

Hiện tại tôi đang sử dụng static_assert để thực thi "Các khái niệm" trên một lớp.

thí dụ:

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

Điều này sẽ gây ra lỗi thời gian biên dịch nếu bất kỳ điều kiện nào ở trên không được đáp ứng.


3
Bây giờ C ++ 11 đã ra mắt (và đã ra mắt được một thời gian), static_assert sẽ được hỗ trợ bởi các phiên bản mới hơn của tất cả các trình biên dịch chính. Đối với những người trong chúng ta, những người không thể chờ đợi C ++ 14 (hy vọng sẽ chứa các ràng buộc mẫu), đây là một ứng dụng rất hữu ích của static_assert.
Collin

7

Một cách sử dụng static_assertcó thể là để đảm bảo rằng cấu trúc (đó là giao diện với thế giới bên ngoài, chẳng hạn như mạng hoặc tệp) có kích thước chính xác mà bạn mong đợi. Điều này sẽ bắt các trường hợp ai đó thêm hoặc sửa đổi một thành viên từ cấu trúc mà không nhận ra hậu quả. Nó static_assertsẽ nhận nó và cảnh báo cho người dùng.


3

Nếu không có khái niệm, người ta có thể sử dụng static_assertđể kiểm tra kiểu thời gian biên dịch đơn giản và dễ đọc, ví dụ, trong các mẫu:

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

// ...
}

2

Điều này không trực tiếp trả lời câu hỏi ban đầu, nhưng thực hiện một nghiên cứu thú vị về cách thực thi các kiểm tra thời gian biên dịch này trước C ++ 11.

Chương 2 (Phần 2.1) của Thiết kế C ++ Hiện đại của Andrei Alexanderscu thực hiện ý tưởng về các xác nhận thời gian biên dịch như thế này

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

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

So sánh macro STATIC_CHECK () và static_assert ()

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

-2

static_assertthể được sử dụng để cấm sử dụng deletetừ khóa theo cách này:

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

Mọi nhà phát triển C ++ hiện đại có thể muốn làm điều đó nếu họ muốn sử dụng trình thu gom rác bảo thủ bằng cách chỉ sử dụng các lớp es và struct s làm quá tải toán tử mới để gọi một hàm phân bổ bộ nhớ trên đống bảo tồn của trình thu gom rác bảo thủ. có thể được khởi tạo và khởi tạo bằng cách gọi một số hàm thực hiện điều này trong phần đầu của mainhàm.

Ví dụ: mọi nhà phát triển C ++ hiện đại muốn sử dụng bộ thu gom rác bảo thủ Boehm-Demers-Weiser sẽ mainviết:

GC_init();

Và trong mọi classstructquá tải operator newtheo cách này:

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

Và bây giờ điều đó operator deletekhông còn cần thiết nữa, vì bộ thu gom rác bảo thủ Boehm-Demers-Weiser chịu trách nhiệm giải phóng và phân bổ mọi khối bộ nhớ khi nó không cần thiết nữa, nhà phát triển muốn cấm deletetừ khóa này.

Một cách là quá tải delete operatortheo cách này:

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

Nhưng điều này không được khuyến khích, bởi vì nhà phát triển C ++ hiện đại sẽ biết rằng anh ấy / cô ấy đã gọi nhầm delete operatorthời gian chạy, nhưng tốt hơn là nên biết điều này sớm vào thời gian biên dịch.

Vì vậy, giải pháp tốt nhất cho tình huống này theo ý kiến ​​của tôi là sử dụng static_assertnhư được hiển thị trong phần đầu của câu trả lời này.

Tất nhiên điều này cũng có thể được thực hiện BOOST_STATIC_ASSERT, nhưng tôi nghĩ điều đó static_asserttốt hơn và nên được ưu tiên hơn luôn luôn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.