BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bsls_assert

Macros

#define BSLS_ASSERT_NO_ASSERTION_MACROS_DEFINED   1
 
#define BSLS_ASSERT_ASSERT_IMP(X, LVL)
 
#define BSLS_ASSERT_ASSUME_IMP(X, LVL)   BSLS_ASSERT_DISABLED_IMP(X,LVL)
 
#define BSLS_ASSERT_DISABLED_IMP(X, LVL)
 
#define BSLS_ASSERT_SAFE(X)
 
#define BSLS_ASSERT_IS_ACTIVE
 
#define BSLS_ASSERT_IS_USED
 
#define BSLS_ASSERT(X)
 
#define BSLS_ASSERT_OPT_IS_ACTIVE
 
#define BSLS_ASSERT_OPT_IS_USED
 
#define BSLS_ASSERT_OPT(X)
 
#define BSLS_ASSERT_INVOKE(X)
 
#define BSLS_ASSERT_INVOKE_NORETURN(X)
 
#define BSLS_ASSERT_NORETURN_INVOKE_HANDLER
 
#define BSLS_ASSERT_RECURSIVELY_INCLUDED_TESTDRIVER_GUARD
 
#define BDE_ASSERT_H(X)   BSLS_ASSERT_SAFE(X)
 
#define BSL_ASSERT_H(X)   BSLS_ASSERT_SAFE(X)
 
#define BDE_ASSERT_CPP(X)   BSLS_ASSERT(X)
 
#define BSL_ASSERT_CPP(X)   BSLS_ASSERT(X)
 
#define BSLS_ASSERT_ASSERT(X)
 

Typedefs

typedef bsls::Assert bdes_Assert
 This alias is defined for backward compatibility.
 
typedef bsls::AssertFailureHandlerGuard bdes_AssertFailureHandlerGuard
 This alias is defined for backward compatibility.
 
typedef bsls::Assert bsls_Assert
 This alias is defined for backward compatibility.
 
typedef bsls::AssertFailureHandlerGuard bsls_AssertFailureHandlerGuard
 This alias is defined for backward compatibility.
 

Detailed Description

Outline

Purpose

Provide build-specific, runtime-configurable assertion macros.

Classes

Macros

See also
bsls_review, bsls_asserttest

Description

This component provides three "assert-like" macros, BSLS_ASSERT, BSLS_ASSERT_SAFE, and BSLS_ASSERT_OPT, that can be used to enable optional redundant runtime checks in corresponding build modes. If an assertion argument evaluates to 0, a runtime-configurable "handler" function is invoked with a bsls::AssertViolation, a value-semantic class that encapsulates the current filename, line number, level of failed check, and (0-valued expression) argument text.

The class bsls::Assert provides functions for manipulating the globally configured "handler". A scoped guard for setting and restoring the assert handler is provided by bsls::AssertFailureHandlerGuard.

An additional macro, BSLS_ASSERT_INVOKE, is provided for direct invocation of the current assertion failure handler. This macro is always enabled (i.e., regardless of build mode).

Defensive Programming (DP)

Although there is no one agreed-upon definition, in this context we will use the term Defensive Programming (DP) to mean any attempt by the component author to provide (optional) runtime validation of the preconditions (or invariants) defined in the function-level documentation (contract) for that component. Note that DP is intended to help expose defects early in the development process, and never to mask or recover from them in production.

Calling a function without satisfying its preconditions results in undefined behavior. Detecting and reporting undefined behavior due to client misuse can sometimes be very helpful at identifying subtle errors. Additionally, we may choose to embed redundant (i.e., logically superfluous) runtime checks – both as a form of active documentation, and also to help expose our own, latent coding errors that have escaped detection during testing. In either case, these defensive (and other) runtime checks can add significant overhead. Hence, this extra runtime overhead should not necessarily be incorporated for every build target and assertion mode (see "Build Modes" below). Moreover, the extent of these checks may change (i.e., for a particular build mode) from one release to the next. Therefore, any defensive (or other redundant) checks provided for a particular build mode are NEVER part of the function-level contract – and remain solely what is known as a Quality-of-Implementation (QoI) issue.

Assertion Semantics

There are three important aspects of assertions: (1) Every assertion is redundant; it is essential that if all assertions are compiled out of a program that is defect-free, apart from improved runtime performance, the program behaves identically. Hence, (2) each !boolean-valued! assert argument must have no !side-effects!. Finally, (3) assertions do not affect binary compatibility; hence, translation units with different assertion levels (but not necessarily build targets) can safely be combined into a single program (see "Build Modes" and "Assertions in Header Files" below). Note that the build target BDE_BUILD_TARGET_SAFE_2 does permit binary incompatibility for conditionally compiled source code, but there is no corresponding BSLS_ASSERT_SAFE_2 assertion macro (see {Usage} below).

Assertion Modes

Depending on the build, assertion macros can expand in 3 different ways:

  1. A bsls_assert macro is "enabled in assert mode", or simply "enabled" if it expands to check its predicate and call the assert failure handler when it is false.
  2. A bsls_assert macro is "enabled in review mode", or simply "in review mode" if it expands to check its predicate and call the review failure handler when it is false. This is identical to a bsls_review macro of the same level when it is enabled.
  3. A bsls_assert macro is "disabled" if it expands to do nothing, producing no executed code in the compiled program.

Review Mode

The ability to enable assertions in review mode allows clients to easily and safely test, in a production environment, whether assertions having a lower threshold than what they currently have deployed are being triggered (without terminating the application). It is intended as an interim step towards lowering the assertion level threshold for an existing application. See bsls_review for a more detailed description of the behavior of assertions in review mode and suggested workflows for using this behavior.

Detailed Behavior

If an assertion fires (i.e., due to a 0-valued expression argument in an assert macro that is enabled or in review mode), there is a violation of the contract that the assertion is checking. If the assertion is enabled, the goal of the assertion is to report the precise location and nature of the defect quickly and loudly and prevent continued execution of the calling function past that point. If the assertion is in review mode then the behavior will match the corresponding bsls_review macro and execution might continue, which has a priority of just logging the failure location.

When enabled, the assert macros will all do essentially the same thing: Each macro tests the predicate expression X, and if !(X) is true, invokes the currently installed assertion failure handler. An instance of bsls::AssertViolation will be created and populated with a textual rendering of the predicate (#X), the current __FILE__, the current __LINE__, and a string representing which particular type of assertion has failed. This violation is then passed to the currently installed assertion failure handler (a function pointer with the type bsls::Assert::ViolationHandler having the signature:

void(const bsls::AssertViolation&);
Definition bsls_assert.h:1929

On some (currently experimental) platforms with support for some form of the upcoming language-level contract facilities there is also the ability to configure the assertion macros to introduce an assumption of the truth of their predicate. With this option the predicate will not neccesarily even be evaluated, and if it were to return false the compiler will treat the situation as undefined behavior ("impossible"). This mode for assertions can lead to improved code generation, but be aware that the potential downside of being wrong about the truth of your assertions is unbounded, and so deploying applications built with any assertions assumed should be done with great care - there are no guarantees about anything a program will do when an assumed assertion is violated.

Selecting Which ASSERT Macro to Use

The choice of which specific macro to use is governed primarily by the impact that enabling the assertion (in either assert mode or review mode) will have on the runtime performance of the function, and in some cases on the size of the function.

  1. BSLS_ASSERT_SAFE - This macro should be reserved for tests incurring an expensive change to the performance of a function, either a very high constant time increase in execution time of the function, or an increase in the algorithmic complexity of a function. Note especially that a change in algorithmic complexity breaks the documented contract of many functions (e.g., an O(n) check in a function with a documented O(log(n)) runtime speed) and so checks with that level of cost should be reserved for diagnostic use in "safe" builds.
  2. BSLS_ASSERT - For "inexpensive" checks with only a constant factor overhead. The majority of checks should fall into this category.
  3. BSLS_ASSERT_OPT - For "negligible" checks that have little to no measurable overhead on a function. This will often be the case for argument checking in larger functions, or very simple checks in smaller functions. Keep in mind that these checks will be enabled in all typically deployed build modes, so they should be reserved for larger functions and functions that will not be called in highly performance critical code.

Assertion and Review Levels

There are a few macros available to control which of the bsls_assert macros are disabled, enabled in review mode, or enabled in assert mode (see {Assertion Modes} above). These macros are for the compilation and build environment to provide and are not themselves defined by BDE code – e.g., by supplying one or more of these macros with -D options on the compiler command line. In general, these macros are used to determine an ASSERT_LEVEL that can be (from most aggressive/optimized to safest) ASSUME_SAFE, ASSUME_ASSERT, ASSUME_OPT, NONE, ASSERT_OPT, ASSERT, or ASSERT_SAFE. Separately, a REVIEW_LEVEL is determined that can be NONE, REVIEW_OPT, REVIEW, or REVIEW_SAFE. Depending on these levels, the various bsls_assert macros will be enabled, in review mode, assumed, or disabled. Macros up to the assert level will be enabled. If the review level is higher than the assert level then macros up to the review level (and above the assert level) will be enabled in review mode. Finally, macros higher than both the review level and the assert level will be disabled. If the review level is NONE and the assert level is set to one of the assume levels, then macros that would be disabled up to the assumed level are instead assumed. If there is a review level set then no macros will ever be assumed. The following table illustrates this:

===========================================
Macro Instantiation Based on Review Level
===========================================
ENABLED - Assertion is enabled (in "assert mode")
REVIEW - Assertion is enabled (in "review mode")
ASSUMED - Assertion is assumed (if supported)
<blank> - Assertion is ignored
-----------BSLS... LEVELS---------- ----------BSLS_.. MACROS---------
BSLS_ASSERT_LEVEL BSLS_REVIEW_LEVEL ASSERT_OPT ASSERT ASSERT_SAFE
----------------- ----------------- ---------- ---------- -----------
ASSUME_SAFE NONE ASSUMED ASSUMED ASSUMED
ASSUME_ASSERT NONE ASSUMED ASSUMED
ASSUME_OPT NONE ASSUMED
NONE NONE
NONE (or ASSUME*) REVIEW_OPT REVIEW
NONE (or ASSUME*) REVIEW REVIEW REVIEW
NONE (or ASSUME*) REVIEW_SAFE REVIEW REVIEW REVIEW
ASSERT_OPT NONE ENABLED
ASSERT_OPT REVIEW_OPT ENABLED
ASSERT_OPT REVIEW ENABLED REVIEW
ASSERT_OPT REVIEW_SAFE ENABLED REVIEW REVIEW
ASSERT NONE ENABLED ENABLED
ASSERT REVIEW_OPT ENABLED ENABLED
ASSERT REVIEW ENABLED ENABLED
ASSERT REVIEW_SAFE ENABLED ENABLED REVIEW
ASSERT_SAFE NONE ENABLED ENABLED ENABLED
ASSERT_SAFE REVIEW_OPT ENABLED ENABLED ENABLED
ASSERT_SAFE REVIEW ENABLED ENABLED ENABLED
ASSERT_SAFE REVIEW_SAFE ENABLED ENABLED ENABLED

See bsls_review for the logic that determines the review level. The logic that determines the assertion level checks a few different macros. The first check is for one of the 7 mutually exclusive BSLS_ASSERT_LEVEL macros that can explicitly set the assert level:

MACRO BSLS_ASSERT_LEVEL
----- ----------------
BSLS_ASSERT_LEVEL_ASSUME_SAFE ASSUME_SAFE
BSLS_ASSERT_LEVEL_ASSUME_ASSERT ASSUME_ASSERT
BSLS_ASSERT_LEVEL_ASSUME_OPT ASSUME_OPT
BSLS_ASSERT_LEVEL_NONE NONE
BSLS_ASSERT_LEVEL_ASSERT_OPT ASSERT_OPT
BSLS_ASSERT_LEVEL_ASSERT ASSERT
BSLS_ASSERT_LEVEL_ASSERT_SAFE ASSERT_SAFE

If none of these are defined, the assert level is determined by the build mode. With "safer" build modes we incorporate higher level defensive checks. A particular build mode is implied by the relevant (BDE) build targets that are defined at compilation (preprocessing) time. The following table shows the three (BDE) build targets that can affect the assertion and review levels:

(BDE) Build Targets
-----------------------
(A) BDE_BUILD_TARGET_SAFE_2
(B) BDE_BUILD_TARGET_SAFE
(C) BDE_BUILD_TARGET_OPT

Any of the 8 possible combinations of the three build targets is valid: e.g., BDE_BUILD_TARGET_OPT and BDE_BUILD_TARGET_SAFE_2 may both be defined. The following table shows the assert level that is set depending on which combination of build target macros have been set:

=========================================================
"ASSERT" Level Set With no Level-Overriding Flags defined
=========================================================
--- BDE_BUILD_TARGET ---- BSLS_ASSERT_LEVEL
_SAFE_2 _SAFE _OPT
------- ------- ------- -----------------
ASSERT
DEFINED ASSERT_OPT
DEFINED ASSERT_SAFE
DEFINED DEFINED ASSERT_SAFE
DEFINED ASSERT_SAFE
DEFINED DEFINED ASSERT_SAFE
DEFINED DEFINED ASSERT_SAFE
DEFINED DEFINED DEFINED ASSERT_SAFE

As the table above illustrates, with no build target explicitly defined the assert level defaults to ASSERT. If only BDE_BUILD_TARGET_OPT is defined, the assert level will be set to ASSERT_OPT. If either BDE_BUILD_TARGET_SAFE or BDE_BUILD_TARGET_SAFE_2 is defined then the assert level is set to ASSERT_SAFE and ALL assert macros will be enabled.

Runtime-Configurable Assertion-Failure Behavior

In addition to the three (BSLS) "ASSERT" macros, BSLS_ASSERT, BSLS_ASSERT_SAFE, and BSLS_ASSERT_OPT, and the immediate invocation macro BSLS_ASSERT_INVOKE, this component provides (1) an invokeHandler method used (primarily) to implement these "ASSERT" macros and enable their runtime configuration, (2) administration methods to configure, at runtime, the behavior resulting from an assertion failure (i.e., by installing an appropriate assertion-failure handler function), and (3) a suite of standard ("off-the-shelf") assertion-failure handler functions, to be installed via the administrative methods (if desired), and invoked by the invokeHandler method on an assertion failure.

When an enabled assertion fails, the currently installed failure handler ("callback") function is invoked. The default handler is the (static) bsls::Assert::failByAbort method; a user may replace this default handler by using the (static) bsls::Assert::setViolationHandler administrative method and passing it (the address of) a function whose signature conforms to the bsls::Assert::ViolationHandler typedef. This handler may be one of the other handler methods provided in bsls::Assert, or a new "custom" function, written by the user (see {Usage} below).

Exception-Throwing Failure Handlers and bsls::AssertFailureHandlerGuard

Among the failure handlers provided is bsls::Assert::failByThrow, which throws a bsls::AssertTestException object. Throwing an exception, however, is not safe in all environments and deliberately aborting is more useful in a debugging context than throwing an unhandled exception. Hence, in order for an bsls::AssertTestException object to be thrown on an assertion failure, the user must first install the bsls::Assert::failByThrow handler (or another exception-throwing handler) explicitly.

Note that an object of type bsls::AssertFailureHandlerGuard can be used to temporarily set an exception-throwing handler within a try block, automatically restoring the previous handler when the try block exits (see {Usage} below).

Assertion Handler Policy

Bloomberg policy is that (by default) tasks may not install an assertion handler that returns control to the point immediately following the detection of a failed assertion. So an assertion handler may, for example, terminate the task or throw an exception, but may not log the problem and return. bsls_assert , by default, enforces that policy by terminating the task if an installed assertion handler function chooses to returns normally.

Configuring an Exception to the Assertion Handler Policy

bsls_assert provides a two-part mechanism to permit returning after the detection of failed assertions.

It is a violation of Bloomberg policy to modify this default configuration without permission from senior management. (Internal Bloomberg users should contact the BDE team if you feel your application needs an exception to this policy).

The intention is to provide a means to override the assertion failure policy that can be enabled quickly, but requires the explicit (and obvious) choice from both the owner of the application's main function, and the person responsible for building the application. In order to enable a policy exception, permitOutOfPolicyReturningFailureHandler must be called, and the task must be linked with a special build of bsls_assert.o (in which the k_permitOutOfPolicyReturningAssertionBuildKey constant has the value "bsls-PermitOutOfPolicyReturn").

Legacy Handler Functions

Prior to the introduction of bsls::AssertViolation, the signature for bsls::Assert::ViolationHandler was this:

void(const char*, const char*,int)

This signature for a handler is still supported (though deprecated) under its original name bsls::Assert::Handler. Overloads that take a bsls::Assert::Handler exist for bsls::AssertFailureHandler and the constructor for bsls::AssertFailureHandlerGuard, so code that uses the old handler signature should work without changes.

If a legacy handler is set as the current handler, the function bsls::Assert::failureHandler() will return a pointer to that function, while bsls::Assert::violationHandler() will return an internal function that extracts the appropriate arguments from the generated bsls::AssertViolation object and passes them to the installed Handler.

Assertions in Header Files (Mixing Build Options Across Translation Units)

Mixing build modes across translation units, although not strictly conformant with the C++ language standard, is permissible in practice; however, the defensive checks that are enabled may be unpredictable. The one-definition rule states that if there are multiple definitions of an object or function within a program, these definitions must be identical or else the program is ill-formed. Unfortunately, definitions in header files may not be identical across object (.o) files if the build targets or assertion-level flags defined during translation (preprocessing) are not the same.

For example, consider an inline function that sets the width of a Square and optionally checks for (defends against) a negative width argument:

// our_square.h
// ...
inline
void Square::setWidth(int width)
{
BSLS_ASSERT_SAFE(width >= 0);
d_width = width;
}
#define BSLS_ASSERT_SAFE(X)
Definition bsls_assert.h:1762

Now consider a client that uses this setWidth method:

// my_client.cpp
// ...
void f()
{
Square s;
s.setWidth(-5);
}

We can build the our_square component in "safe mode" – e.g., by incorporating -DBSLS_ASSERT_LEVEL_ASSERT_SAFE on the (Unix) command line. Notice, however, that building client software against a version of our_square.o compiled in "safe mode" does not ensure that all of the BSLS_ASSERT_SAFE macros will be active (will instantiate); instead, the client's build mode will (most likely) govern those instantiations of the BSLS_ASSERT_SAFE macro located within the library. The only way to ensure that all of the BSLS_ASSERT_SAFE macros instantiate is to build the client as well as the library software in "safe mode".

Inline functions are not the only source of multiple inconsistent definitions. Consider a non-inline method reserveCapacity on a List template, parameterized by element TYPE:

// our_list.h
// ...
template <class TYPE>
void List<TYPE>::reserveCapacity(int numElements)
{
BSLS_ASSERT(numElements >= 0);
// ...
}
#define BSLS_ASSERT(X)
Definition bsls_assert.h:1804

Each different translation unit that invokes reserveCapacity potentially generates another instantiation of this function template. Those translation units that are compiled in "debug mode" (or "safe mode") – e.g., with BSLS_ASSERT_LEVEL_ASSERT (or BSLS_ASSERT_LEVEL_ASSERT_SAFE) defined – will incorporate code corresponding to each use of the BSLS_ASSERT macro therein; the rest will not. Which one of these template instantiations the linker uses in the final program is undefined and highly unpredictable.

The bottom line is that, unless clients of a library are compiled with (at least) the same level of assertion enabling as the library itself, not all of the library's defensive checking (for the assertion-level for which the library was compiled) will necessarily be incorporated into the client code. Similarly, compiling a client in a higher-level of defensive checking (e.g., "safe mode") than the library was compiled (e.g., "debug mode") may result in additional defensive checks beyond what the library author intended for the mode (e.g., "debug mode") in which the library was compiled.

Note that all build modes (except for when BDE_BUILD_TARGET_SAFE_2 is defined, see below) are required to be binary compatible (e.g., fields cannot be added to the middle of a struct). Since a component's contract makes no explicit promise about what checking will occur, that contract is not violated when different parts of a program are compiled with different levels of assertion-enabling build options. The only consequence is that a smaller (or larger) number of defensive checks may be active than might otherwise be expected.

Conditional Compilation

To recap, there are three (mutually compatible) general build targets:

The public interface of this component also explicitly provides a number of additional intermediate macros to identify how the various BSLS_ASSERT macros have been instantiated. These each exist for each level and have the following suffixes and meanings:

Putting that together, these 3 macros are defined if the corresponding macro is in assert or review mode - and thus the expression will be checked and a violation handler will be invoked on failure:

Finally, three more macros with the IS_USED suffix are defined when the expression for the corresponding macro is going to be compiled. This will be true for macros in assert, review or assumed modes, and it will be true for all macros if BSLS_ASSERT_VALIDATE_DISABLED_MACROS has been defined.

Note that any of the IS_ACTIVE, IS_REVIEW", and</tt>IS_ASSUMED' macros being defined will imply that the corresponding <tt>IS_USED</tt> macro is also defined. Which of these macros to use to conditionally compile supporting code is based on when that supporting code needs to be compiled: * Use <tt>\#if defined(..._IS_USED)</tt> when: * Writing functions that are only accessible to and needed for assertions of the corresponding level. This could be private member functions, static functions, or functions in an anonymous namespace. See {Example 8} for details on this use. * Use <tt>\#if !defined(..._IS_ACTIVE) \&\& !defined(..._IS_ASSUMED)</tt> when: * You are writing (test) code that will intentionally violate a contract when there is not going to be any intrinsic ill effect to that violation. Generally this should only be required when there is a need to validate out-of-contract behavior of a component from within its own test driver. * Use <tt>\#if defined(...IS_ACTIVE)</tt> when: * You are doing negative testing and want to be sure that when you call your function out of contract that the violation handler will be invoked. See @ref bsls_asserttest for tools to do this without having to manually check these macros. * Writing redundant defensive code that should only execute when the corresponding assertions are going to be enabled. The assertion itself should also be included in the same preprocessor block. See {Example 9} for details on this use. * Note that historically this was the only macro available, and it is often used for blocks of code where the checks above would be more appropriate. This can often lead to code that fails to compile with <tt>BSLS_ASSERT_VALIDATE_DISABLED_MACROS</tt> enabled or which will not work correctly when assumptions are turned on. See {Example 6} and {Example 7}, respectively, for how <tt>BDE_BUILD_TARGET_SAFE_2</tt> and intermediate assertion predicate macros, such as <tt>BSLS_ASSERT_SAFE_IS_ACTIVE</tt> (and even <tt>BSLS_ASSERT_OPT_IS_ACTIVE</tt>), can be used profitably in practice. @subsection bsls_assert-validating-disabled-macro-expressions Validating Disabled Macro Expressions An additional external macro, <tt>BSLS_ASSERT_VALIDATE_DISABLED_MACROS</tt>, can be defined to control the compile time behavior of @ref bsls_assert . Enabling this macro configures all <em>disabled</em> assert macros to still instantiate their predicates (in a non-evaluated context) to be sure that the predicate is still syntactically valid. This can be used to ensure assertions that are rarely enabled have valid expressions. @subsection bsls_assert-language-level-contracts Language-Level Contracts Contracts were proposed, accepted into the draft C++20 standard, and then removed. Implementations of that facility exist and it is expected future implementations will begin to arrive as work on new proposals comes to fruition. Defining the macro <tt>BSLS_ASSERT_USE_CONTRACTS</tt> will cause all <tt>BSLS_ASSERT</tt> (and, if possible, <tt>BSLS_REVIEW</tt>) macros to go through the language-level contract implementation if it is available (currently only on an experimental version of the gcc-compiler), otherwise a diagnostic will be issued. Note that mixing builds that do and do not use <tt>BSLS_ASSERT_USE_CONTRACTS</tt> is not supported. Attempting to link against a library bult with a different mode for this option will cause a link-time error. @subsection bsls_assert-usage Usage The following examples illustrate (1) when to use each of the three kinds of (BSLS) "ASSERT" macros, (2) when and how to call the <tt>invokeHandler</tt> method directly, (3) how to configure, at runtime, the behavior resulting from an assertion failure using "off-the-shelf" handler methods, (4) how to create your own custom assertion-failure handler function, (5) proper use of <tt>bsls::AssertFailureHandlerGuard</tt> to install, temporarily, an exception-producing assert handler, (6) how "ASSERT" macros would be used in conjunction with portions of the source code (affecting binary compatibility) that are incorporated only when <tt>BDE_BUILD_TARGET_SAFE_2</tt> is defined, and (7) how assertion predicates (e.g., <tt>BSLS_ASSERT_SAFE_IS_ACTIVE</tt>) are used to conditionally compile additional (redundant) defensive source code (not affecting binary compatibility) precisely when the corresponding (BSLS) "ASSERT" macro (e.g., <tt>BSLS_ASSERT_SAFE</tt>) is active. @subsubsection bsls_assert-example-1-using-bsls_assert-bsls_assert_safe-and-bsls_assert_opt Example 1: Using BSLS_ASSERT, BSLS_ASSERT_SAFE, and BSLS_ASSERT_OPT This component provides three different variants of (BSLS) "ASSERT" macros. This first usage example illustrates how one might select each of the particular variants, based on the runtime cost of the defensive check relative to that of the useful work being done. Use of the <tt>BSLS_ASSERT_SAFE</tt> macro is often appropriate when the defensive check occurs within the body of an <tt>inline</tt> function. The <tt>BSLS_ASSERT_SAFE</tt> macro minimizes the impact on runtime performance as it is instantiated only when requested (i.e., by building in "safe mode"). For example, consider a light-weight point class <tt>Kpoint</tt> that maintains <tt>x</tt> and <tt>y</tt> coordinates in the range <tt>[-1000 .. 1000]</tt>: @code my_kpoint.h ... class Kpoint { short int d_x; short int d_y; public: Kpoint(short int x, short int y); // ... // The behavior is undefined unless '-1000 <= x <= 1000' and // '-1000 <= y <= 1000'. // ... }; ... @endcode Since the cost of validation here is significant compared with the useful work being done, we might choose to implement defensive checks using <tt>BSLS_ASSERT_SAFE</tt> as follows: @code ... inline Kpoint::Kpoint(short int x, short int y) : d_x(x) , d_y(y) { BSLS_ASSERT_SAFE(-1000 <= x); BSLS_ASSERT_SAFE(x <= 1000); BSLS_ASSERT_SAFE(-1000 <= y); BSLS_ASSERT_SAFE(y <= 1000); } @endcode For more substantial (non-<tt>inline</tt>) functions, we would be more likely to use the <tt>BSLS_ASSERT</tt> macro because the runtime overhead due to defensive checks is likely to be much less significant. For example, consider a hash-table class that allows the client to resize the underlying table: @code my_hashtable.h ... class HashTable { // ... public: // ... void resize(double loadFactor); // Adjust the size of the underlying hash table to be approximately // the current number of elements divided by the specified // 'loadFactor'. The behavior is undefined unless // '0 < loadFactor'. }; @endcode Since the relative runtime cost of validating the input argument is quite small (e.g., less than 10%) compared to the typical work being done, we might choose to implement the defensive check using <tt>BSLS_ASSERT</tt> as follows: @code my_hashtable.cpp ... void HashTable::resize(double loadFactor) { BSLS_ASSERT(0 < loadFactor); // ... } @endcode In some cases, the runtime cost of checking is always negligible when compared with the runtime cost of performing the useful work; moreover, the consequences of continuing in an undefined state for certain applications could be catastrophic. Instead of using <tt>BSLS_ASSERT</tt> in such cases, we might consider using <tt>BSLS_ASSERT_OPT</tt>. For example, suppose we have a financial application class <tt>TradingSystem</tt> that performs trades: @code my_tradingsystem.h ... class TradingSystem { // ... public: // ... @endcode Further suppose that there is a particular method <tt>executeTrade</tt> that takes, as a scaling factor, an integer that must be a multiple of 100 or the behavior is undefined (and might actually execute a trade): @code void executeTrade(int scalingFactor); // Execute the current trade using the specified 'scalingFactor'. // The behavior is undefined unless '0 <= scalingFactor' and '100' // evenly divides 'scalingFactor'. // ... }; @endcode Because the cost of the two checks is likely not even measurable compared to the overhead of accessing databases and executing the trade, and because the consequences of specifying a bad scaling factor are virtually unbounded, we might choose to implement these defensive checks using <tt>BSLS_ASSERT_OPT</tt> as follows: @code my_tradingsystem.cpp ... void TradingSystem::executeTrade(int scalingFactor) { BSLS_ASSERT_OPT(0 <= scalingFactor); BSLS_ASSERT_OPT(0 == scalingFactor % 100); // ... } @endcode Notice that in each case, the choice of which of the three (BSLS) "ASSERT" macros to use is governed primarily by the relative runtime cost compared with that of the useful work being done (and only secondarily by the potential consequences of continuing execution in an undefined state). @subsubsection bsls_assert-example-2-when-and-how-to-call-the-invokehandler-method-directly Example 2: When and How to Call the invokeHandler Method Directly There <em>may</em> be times (but this is yet to be demonstrated) when we might reasonably choose to unconditionally invoke the currently installed assertion-failure handler directly &ndash; i.e., instead of via one of the three (BSLS) "ASSERT" macros provided in this component. Suppose that we are currently in the body of some function <tt>someFunc</tt> and, for whatever reason, feel compelled to invoke the currently installed assertion-failure handler based on some criteria other than the current build mode. <tt>BSLS_ASSERT_INVOKE</tt> is provided for this purpose. The call might look as follows: @code void someFunc(bool a, bool b, bool c) { bool someCondition = a && b && !c; if (someCondition) { BSLS_ASSERT_INVOKE("Bad News"); } } @endcode If presented with invalid arguments, <tt>someFunc</tt> (above) will produce output similar to the following: @code Assertion failed: Bad News, file bsls_assert.t.cpp, line 609 Abort (core dumped) @endcode If a piece of code needs to be guaranteed to not return, the additional macro <tt>BSLS_ASSERT_INVOKE_NORETURN</tt> is also available. It behaves the same way as <tt>BSLS_ASSERT_INVOKE</tt>, but if the installed handler <em>does</em> return <tt>failByAbort</tt> will be immediately called. On supported platforms it is marked appropriately to not return to support compiler requirements and static analysis tools. @subsubsection bsls_assert-example-3-runtime-configuration-of-the-bsls-assert-facility Example 3: Runtime Configuration of the bsls::Assert Facility By default, any assertion failure will result in the invocation of the <tt>bsls::Assert::failByAbort</tt> handler function. We can replace this behavior with that of one of the other static failure handler methods supplied in <tt>bsls::Assert</tt> as follows. Let's assume we are at the top of our application called <tt>myMain</tt> (which would typically be <tt>main</tt>): @code void myMain() { @endcode First observe that the default assertion-failure handler function is, in fact, <tt>bsls::Assert::failByAbort</tt>: @code assert(&bsls::Assert::failByAbort == bsls::Assert::violationHandler()); @endcode Next, we install a new assertion-failure handler function, <tt>bsls::Assert::failBySleep</tt>, from the suite of "off-the-shelf" handlers provided as <tt>static</tt> methods of <tt>bsls::Assert</tt>: @code bsls::Assert::setViolationHandler(&bsls::Assert::failBySleep); @endcode Observe that <tt>bsls::Assert::failBySleep</tt> is the new, currently-installed assertion-failure handler: @code assert(&bsls::Assert::failBySleep == bsls::Assert::violationHandler()); @endcode Note that if we were to explicitly invoke the current assertion-failure handler as follows: @code BSLS_ASSERT_INVOKE("message"); // This will hang! @endcode the program will hang since <tt>bsls::Assert::failBySleep</tt> repeatedly sleeps for a period of time within an infinite loop. Thus, this assertion-failure handler is useful for hanging a process so that a debugger may be attached to it. We may now decide to disable the <tt>setViolationHandler</tt> method using the <tt>bsls::Assert::lockAssertAdministration()</tt> method to ensure that no one else will override our decision globally. Note, however, that the <tt>bsls::AssertFailureHandlerGuard</tt> is not affected, and can still be used to supplant the currently installed handler (see below): @code bsls::Assert::lockAssertAdministration(); @endcode Attempting to change the currently installed handler now will fail: @code bsls::Assert::setViolationHandler(&bsls::Assert::failByAbort); assert(&bsls::Assert::failByAbort != bsls::Assert::violationHandler()); assert(&bsls::Assert::failBySleep == bsls::Assert::violationHandler()); } @endcode @subsubsection bsls_assert-example-4-creating-a-custom-assertion-handler Example 4: Creating a Custom Assertion Handler Sometimes, especially during testing, we may need to write our own custom assertion-failure handler function. The only requirements are that the function have the same prototype (i.e., the same respective parameter and return types) as the <tt>bsls::Assert::Handle</tt> <tt>typedef</tt>, and that the function should not return (i.e., it must <tt>abort</tt>, <tt>exit</tt>, <tt>terminate</tt>, <tt>throw</tt>, or hang). To illustrate, we will create a <tt>static</tt> method at file scope that conforms to the required structure (notice the explicit use of <tt>std::printf</tt> from <tt>\<cstdio\></tt> instead of <tt>std::cout</tt> from <tt>\<iostream\></tt> to avoid interaction with the C++ memory allocation layer): @code static bool globalEnableOurPrintingFlag = true; static void ourFailureHandler(const bsls::AssertViolation& violation) // Print the expression 'comment', 'file' name, and 'line' number from // the specified 'violation' to 'stdout' as a comma-separated list, // replacing null string-argument values with empty strings (unless // printing has been disabled by the 'globalEnableOurPrintingFlag' // variable), then unconditionally abort. { const char *comment = violation.comment(); if (!comment) { comment = ""; } const char *file = violation.fileName(); if (!file) { file = ""; } int line = violation.lineNumber(); if (globalEnableOurPrintingFlag) { std::printf("s, s, d
", comment, file, line); } std::abort(); } @endcode At the top level of our application we have the following: @code void ourMain() { @endcode First, let's observe that we can assign this new function to a function pointer of type <tt>bsls::Assert::Handler</tt>: @code bsls::Assert::ViolationHandler f = &ourFailureHandler; @endcode Now we can install it just as we would any other handler: @code bsls::Assert::setViolationHandler(&ourFailureHandler); @endcode We can now invoke the default handler directly: @code BSLS_ASSERT_INVOKE("str1"); } @endcode With the resulting output something like as follows: @code str1, my_file.cpp, 17 Abort (core dumped) @endcode @subsubsection bsls_assert-example-5-using-the-bsls-assertfailurehandlerguard Example 5: Using the bsls::AssertFailureHandlerGuard Sometimes we may want to replace, temporarily (i.e., within some local lexical scope), the currently installed assertion-failure handler function. In particular, we sometimes use the <tt>bsls::AssertFailureHandlerGuard</tt> class to replace the current handler with one that throws an exception (because we know that such an exception is safe in the local context). Let's start with the simple factorial function below, which validates, in "debug mode" (or "safe mode"), that its input is non-negative: @code double fact(int n) // Return 'n!'. The behavior is undefined unless '0 <= n'. { BSLS_ASSERT(0 <= n); double result = 1.0; while (n > 1) { result *= n--; } return result; } @endcode Now consider the following integer-valued <tt>extern "C"</tt> C++ function, <tt>wrapperFunc</tt>, which can be called from C and FORTRAN, as well as from C++: @code extern "C" int wrapperFunc(bool verboseFlag) { enum { GOOD = 0, BAD } result = GOOD; (void) verboseFlag; @endcode The purpose of this function is to allow assertion failures in subroutine calls below this function to be handled by throwing an exception, which is then caught by the wrapper and reported to the caller as a "bad" status. Hence, when within the runtime scope of this function, we want to install, temporarily, the assertion-failure handler <tt>bsls::Assert::failByThrow</tt>, which, when invoked, causes an <tt>bsls::AssertTestException</tt> object to be thrown. (Note that we are not advocating this approach for "recovery", but rather for an orderly shut-down, or perhaps during testing.) The <tt>bsls::AssertFailureHandlerGuard</tt> class is provided for just this purpose: @code assert(&bsls::Assert::failByAbort == bsls::Assert::violationHandler()); bsls::AssertFailureHandlerGuard guard(&bsls::Assert::failByThrow); assert(&bsls::Assert::failByThrow == bsls::Assert::violationHandler()); @endcode Next we open up a <tt>try</tt> block, and somewhere within the <tt>try</tt> we "accidentally" invoke <tt>fact</tt> with an out-of-contract value (i.e., <tt>-1</tt>): @code #ifdef BDE_BUILD_TARGET_EXC try #endif { // ... double d = fact(-1); // Out-of-contract call to 'fact'. // ... } #ifdef BDE_BUILD_TARGET_EXC catch (const bsls::AssertTestException& e) { result = BAD; if (verboseFlag) { std::printf( "Internal Error: s, s, d
", e.expression(), e.filename(), e.lineNumber() ); } } #endif return result; } @endcode Assuming exceptions are enabled (i.e., <tt>BDE_BUILD_TARGET_EXC</tt> is defined), if an <tt>bsls::AssertTestException</tt> occurs below this wrapper function, the exception will be caught, a message will be printed to <tt>stdout</tt>, e.g., @code Internal Error: bsls_assert.t.cpp:500: 0 <= n @endcode and the <tt>wrapperFunc</tt> function will return a bad status (i.e., 1) to its caller. Note that if exceptions are not enabled, <tt>bsls::Assert::failByThrow</tt> will behave as <tt>bsls::Assert::failByAbort</tt>, and dump core immediately: @code Assertion failed: 0 <= n, file bsls_assert.t.cpp, line 500 Abort (core dumped) @endcode Finally note that the <tt>bsls::AssertFailureHandlerGuard</tt> is not thread-aware. In particular, a guard that is created in one thread will also affect the failure handlers that are used in other threads. Care should be taken when using this guard when more than a single thread is executing. @subsubsection bsls_assert-example-6-using-assert-macros-along-with-bde_build_target_safe_2 Example 6: Using (BSLS) "ASSERT" Macros Along With BDE_BUILD_TARGET_SAFE_2 Recall that assertions do not affect binary compatibility; however, software built with <tt>BDE_BUILD_TARGET_SAFE_2</tt> defined need not be binary compatible with software built otherwise. In this example, we look at how we might use the (BSLS) "ASSERT" family of macros in conjunction with code that is incorporated (at compile time) only when <tt>BDE_BUILD_TARGET_SAFE_2</tt> is defined. As a simple example, let's consider an elided implementation of a singly-linked integer list and its iterator. Whenever <tt>BDE_BUILD_TARGET_SAFE_2</tt> is defined, we want to defend against the possibility that a client mistakenly passes a <tt>ListIter</tt> object into a <tt>List</tt> object method (e.g., <tt>List::insert</tt>) where that <tt>ListIter</tt> object did not originate from the same <tt>List</tt> object. We'll start by defining a local helper <tt>List_Link</tt> <tt>struct</tt> as follows: @code struct List_Link { List_Link *d_next_p; int d_data; List_Link(List_Link *next, int data) : d_next_p(next), d_data(data) { } }; @endcode Next, we'll define <tt>ListIter</tt>, which always identifies the current position in a sequence of links, but whenever <tt>BDE_BUILD_TARGET_SAFE_2</tt> is defined, also maintains a pointer to its parent <tt>List</tt> object: @code class List; // Forward declaration. class ListIter { #ifdef BDE_BUILD_TARGET_SAFE_2 List *d_parent_p; // Exists only in "safe 2 mode". #endif List_Link **d_current_p; friend class List; friend bool operator==(const ListIter&, const ListIter&); private: ListIter(List_Link **current, List * #ifdef BDE_BUILD_TARGET_SAFE_2 parent // Not used unless in "safe 2 mode". #endif ) : d_current_p(current) #ifdef BDE_BUILD_TARGET_SAFE_2 , d_parent_p(parent) // Initialize only in "safe 2 mode". #endif { } public: ListIter& operator++() { /* ... */ return *this; } // ... }; bool operator==(const ListIter& lhs, const ListIter& rhs); bool operator!=(const ListIter& lhs, const ListIter& rhs); @endcode Finally we define the <tt>List</tt> class itself with most of the operations elided; the methods of particular interest here are <tt>begin</tt> and <tt>insert</tt>: @code class List { List_Link *d_head_p; public: // CREATORS List() : d_head_p(0) { } List(const List&) { /* ... */ } ~List() { /* ... */ } // MANIPULATORS List& operator=(const List&) { /* ... */ return *this; } //| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | //v v v v v v v v v v v v v v v v v v v v v v v v v v v v v v v v v v v //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ListIter begin() // Return an iterator referring to the beginning of this list. { return ListIter(&d_head_p, this); } //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | //v v v v v v v v v v v v v v v v v v v v v v v v v v v v v v v v v v v //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: void insert(const ListIter& position, int data) // Insert the specified 'data' value into this list at the // specified 'position'. { #ifdef BDE_BUILD_TARGET_SAFE_2 BSLS_ASSERT_SAFE(this == position.d_parent_p); // "safe 2 mode" #endif *position.d_current_p = new List_Link(*position.d_current_p, data); } //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: // ACCESSORS void print() // Output the contents of this list to 'stdout'. { printf( "[" ); for (List_Link *p = d_head_p; p; p = p->d_next_p) { printf( " d", p->d_data ); } printf(" ]
"); } }; @endcode Outside of "safe 2 mode", it is possible to pass an iterator object obtained from the <tt>begin</tt> method of one <tt>List</tt> object into the <tt>insert</tt> method of another, having, perhaps, unexpected results: @code void sillyFunc(bool printFlag) { List a; ListIter aIt = a.begin(); a.insert(aIt, 1); a.insert(aIt, 2); a.insert(aIt, 3); if (printFlag) { std::printf( "a = "); a.print(); } List b; ListIter bIt = b.begin(); a.insert(bIt, 4); // Oops! Should have been: 'b.insert(bIt, 4);' a.insert(bIt, 5); // Oops! " " " ' " " 5 ' a.insert(bIt, 6); // Oops! " " " ' " " 6 ' if (printFlag) { std::printf( "a = "); a.print(); std::printf( "b = "); b.print(); } } @endcode In the example above, we have "accidentally" passed the iterator <tt>bIt</tt> obtained from <tt>List</tt> object <tt>b</tt> into the <tt>insert</tt> method for <tt>List</tt> object <tt>a</tt>. The resulting undefined behavior (in other than "safe 2 mode") might produce output that looks as follows: @code a = [ 3 2 1 ] a = [ 3 2 1 ] b = [ 6 5 4 ] @endcode If the same <tt>sillyFunc</tt> were compiled in "safe 2 mode" (i.e., with <tt>BDE_BUILD_TARGET_SAFE_2</tt> defined) the undefined behavior would be detected and the output would, by default, look more like the following: @code a = [ 3 2 1 ] FATAL my_list.cpp:56 Assertion failed: this == position.d_parent_p Abort (core dumped) @endcode thereby quickly exposing the misuse by the client. @subsubsection bsls_assert-example-7-conditional-compilation-associated-with-enabled-assertion-levels Example 7: Conditional Compilation Associated with Enabled Assertion Levels In cases where we want to tie code, other than just an assertion, to a specific level of enabled assertions, we will want to use the corresponding intermediate predicate that enables that level of assertions: * For <tt>BSLS_ASSERT_SAFE</tt>, use <tt>BSLS_ASSERT_SAFE_IS_ACTIVE</tt>. * For <tt>BSLS_ASSERT</tt>, use <tt>BSLS_ASSERT_IS_ACTIVE</tt>. * For <tt>BSLS_ASSERT_OPT</tt>, use <tt>BSLS_ASSERT_OPT_IS_ACTIVE</tt>. Suppose that we have a class such as <tt>MyDate</tt> (below) that, except for checking its invariants, would have a trivial destructor. By not declaring a destructor at all, we may realize performance advantages, but then we lose the ability to validate our invariants in "debug" or "safe" mode. What we want to do is to declare (and later define) the destructor in precisely those build modes for which we would want to assert invariants. An elided class <tt>MyDate</tt>, which is based on a serial-date implementation, is provided for reference: @code class MyDate { // This class implements a value-semantic "date" type representing // valid date values in the range '[ 0001Jan01 .. 9999Dec31 ]'. // DATA int d_serialDate; // sequential representation within a valid range public: // CLASS METHODS // ... // CREATORS MyDate(); // Create a 'MyDate' object having the value '0001Jan01'. // ... MyDate(const MyDate& original); // Create a 'MyDate' object having the same value as the specified // 'original' object. #if defined(BSLS_ASSERT_SAFE_IS_ACTIVE) ~MyDate(); // Destroy this object. Note that in some build modes the // destructor generated by the compiler is trivial. #endif // ... }; ... ======================================================================== INLINE FUNCTION DEFINITIONS ======================================================================== ... CREATORS inline MyDate::MyDate() : d_serialDate(1) // 0001Jan01 { } inline MyDate::MyDate(const MyDate& original) : d_serialDate(original.d_serialDate) { } ... #if defined(BSLS_ASSERT_SAFE_IS_ACTIVE) inline MyDate::~MyDate() { BSLS_ASSERT_SAFE(1 <= d_serialDate); // 0001Jan01 BSLS_ASSERT_SAFE( d_serialDate <= 3652061); // 9999Dec31 } #endif ... @endcode In practice, however, we would probably implement an <tt>isValidSerialDate</tt> method in a lower-level utility class, e.g., <tt>MyDateImpUtil</tt>, leading to code that is more fine-grained, modular, and hierarchically reusable: @code struct MyDateImpUtil { static bool isValidSerialDate(int d_date); // Return 'true' if the specified 'd_date' represents a valid date // value, and 'false' otherwise. }; inline bool MyDateImpUtil::isValidSerialDate(int d_date) { return 1 <= d_date && d_date <= 3652061; } @endcode Like other aspects of <tt>BSLS_ASSERT_SAFE</tt>, the example above violates the one-definition rule for mixed-mode builds. Note that all code conditionally compiled based on <tt>BSLS_ASSERT_SAFE_IS_ACTIVE</tt>, <tt>BSLS_ASSERT_IS_ACTIVE</tt>, and <tt>BSLS_ASSERT_OPT_IS_ACTIVE</tt> should be binary compatible for mixed-mode builds. If the conditionally-compiled code would not be binary compatible, use <tt>BDE_BUILD_TARGET_SAFE_2</tt> instead. WARNING - In practice, declaring a destructor in some build modes but not others has led to subtle and difficult-to-diagnose failures. DON'T DO IT! Finally, in very rare cases, we may want to put in (redundant) defensive code (in the spirit of <tt>BSLS_ASSERT_OPT</tt>) that is not part of the component-level contract, yet (1) is known to have negligible runtime cost and (2) is deemed to be so important as to be necessary even for optimized builds. For example, consider again the <tt>MyDate</tt> class above that now also declares a non-<tt>inline</tt> <tt>print</tt> method to format the current date value in some human-readable, but otherwise unspecified format: @code xyza_mydate.h ... class MyDate { // ... // DATA int d_serialDate; // sequential representation within a valid range public: // ... // ACCESSORS // ... std::ostream& print(std::ostream& stream, ...) const; // Write the value of this object to the specified output 'stream' // in some human-readable format, and return a reference to // 'stream'. Optionally specify ... // ... }; @endcode Successfully writing bad data is among the most insidious of bugs, because a latent error can persist and not be discovered until long after the program terminates. Writing the value of a corrupted <tt>MyDate</tt> object in a <em>machine-readable</em> (binary) format is an error so serious as to warrant invoking @code void testFunction(int d_serialDate) { BSLS_ASSERT_OPT(MyDateImpUtil::isValidSerialDate(d_serialDate)); } @endcode each time we attempt the output operation; however, printing the value in a human-readable format intended primarily for debugging purposes is another matter. In anything other than a safe build (which in this case would enforce essentially all method preconditions), it would be unfortunate if a developer, knowing that there was a problem involving the use of <tt>MyDate</tt>, inserted print statements to identify that problem, only to have the <tt>print</tt> method itself ruthlessly invoke the assert handler, likely terminating the process). Moreover, it may also be unsafe even to attempt to format the value of a <tt>MyDate</tt> object whose <tt>d_serialDate</tt> value violates its invariants (e.g., due to a static table lookup). In such cases we may, as sympathetic library developers, choose to implement different undefined (undocumented) redundant defensive behaviors, depending on the desired level of assertions: @code xyza_mydate.cpp ... #include <xyza_mydateimputil.h> ... std::ostream& MyDate::print(std::ostream& stream, ...) const { // BSLS_ASSERT(/* any *argument* preconditions for this function */); // Handle case where the invariants have been violated. #ifdef BSLS_ASSERT_OPT_IS_ACTIVE // Note that if 'BSLS_ASSERT_LEVEL_NONE' has been set, this code -- // along with all 'BSLS_ASSERT_OPT' macros -- will not instantiate, // enabling us to verify that the combined runtime overhead of all such // (redundant) defensive code is at worst negligible, if not // unmeasurable. if (!MyDateImpUtil::isValidSerialDate(d_serialDate)) { // Our invariant is corrupted. #ifdef BSLS_ASSERT_IS_ACTIVE // Providing debugging information in this mode would be useful. std::cerr << "\nxyza::MyDate: Invalid internal serial date value " << d_serialDate << '.' << std::endl; #endif // BSLS_ASSERT_IS_ACTIVE // In safe mode, each of the 'MyClass' methods fully guards its // preconditions: There is simply no easy way to get here! BSLS_ASSERT_SAFE("Probable rogue memory overwrite!" && 0); // If we get here, we're corrupted, but not in safe mode! return stream << "(* Invalid 'MyDate' State " << d_serialDate << " *)" << std::flush; // RETURN } #endif // BSLS_ASSERT_OPT_IS_ACTIVE // If we get here in a production build, this object is "sane": Do // whatever this 'print' method would normally do, assuming that no // method preconditions or object invariants are violated.

// ... <*** Your (Normal-Case) Formatting Code Here! ***>

return stream; }

Example 8: Conditional Compilation of Support Functions

Occasionally a function may exist only to support a specific set of assertions. Often this can happen when a large expression that captures a complicated precondition wants to be refactored into a distinct location to ease understanding of it. When this happens the function might still remain as a private implementation detail of the class.

When the only assertion macros that use the function are disabled this can lead to a compiler warning about a function being unused, and the corresponding code bloat from having the function available might be an overhead that is not desired.

In order to totally remove the function when it is not needed, the IS_USED suffixed macros can be used to guard the declaration and definition of the function. Suppose we have a class with a function having a complex precondition, and that precondition check is both private and only needed when the assertions that use it are enabled. In that case, we can guard the definitions and declarations against even being compiled like this:

class ComplexObject {
// ...
#if defined(BSLS_ASSERT_SAFE_IS_USED)
bool isPurplish() const;
// Return 'true' if the current state of this object fits within
// the complex requirements of being sufficiently purple, false
// otherwise.
#endif
// ...
public:
// MANIPULATORS
void doSomethingPurpley();
// Do something purpley. The behavior is undefined unless this
// object is currently purplish (contact customer support to know
// the current threshholds for purplishness).
};
#if defined(BSLS_ASSERT_SAFE_IS_USED)
bool ComplexObject::isPurplish() const
{
// The real implementation would encode the complex logic of needing to
// determine if this object feels purplish at the moment.
return true;
}
#endif
void ComplexObject::doSomethingPurpley()
{
BSLS_ASSERT_SAFE(isPurplish());
}

Now, the ComplexObject::isPurplish function will only exist in a subset of builds:

Example 9: Conditional Compilation of Support Code

Sometimes there is code that needs to run in a function before an assertion to gather information needed only by that assertion. Often this can be capturing input values or other system state before it is modified and verifying at the end of a function that the values are changed (or not) appropriately.

When the corresponding assertion macro is not active in assert or review mode the supporting code should not be executed at all. Importantly, because the capturing of additional information is an extra cost, the assertion itself does not lend itself to being assumed.

Suppose we have a function that wishes to swap the values of its input:

struct MySwapper {
template <class T>
static void swap(T& lhs, T& rhs)
// Exchange the values of the specified 'lhs' and 'rhs'.
{
T tmp = lhs;
lhs = rhs;
rhs = tmp;
}
};

This works great as a simple swap implementation, but we would like to assert in safe mode that it is doing the correct thing. In order to do that we need to capture the initial values of our inputs before doing anything else, and we want to do this only when the respective assertions are enabled. Here we would guard our code and our assertions in a check that BSLS_ASSERT_SAFE_IS_ACTIVE is defined, like this:

struct MySwapper {
template <class T>
static void swap(T& lhs, T& rhs)
// Exchange the values of the specified 'lhs' and 'rhs'.
{
#if defined(BSLS_ASSERT_SAFE_IS_ACTIVE)
T origLhs(lhs);
T origRhs(rhs);
#endif
T tmp = lhs;
lhs = rhs;
rhs = tmp;
#if defined(BSLS_ASSERT_SAFE_IS_ACTIVE)
BSLS_ASSERT_SAFE(rhs == origLhs);
BSLS_ASSERT_SAFE(lhs == origRhs);
#endif
}
};

Macro Definition Documentation

◆ BDE_ASSERT_CPP

#define BDE_ASSERT_CPP (   X)    BSLS_ASSERT(X)

◆ BDE_ASSERT_H

#define BDE_ASSERT_H (   X)    BSLS_ASSERT_SAFE(X)

◆ BSL_ASSERT_CPP

#define BSL_ASSERT_CPP (   X)    BSLS_ASSERT(X)

◆ BSL_ASSERT_H

#define BSL_ASSERT_H (   X)    BSLS_ASSERT_SAFE(X)

◆ BSLS_ASSERT

#define BSLS_ASSERT (   X)
Value:
X, \
BloombergLP::bsls::Assert::k_LEVEL_ASSERT)
#define BSLS_ASSERT_ASSERT_IMP(X, LVL)
Definition bsls_assert.h:1668

◆ BSLS_ASSERT_ASSERT

#define BSLS_ASSERT_ASSERT (   X)
Value:
X, \
BloombergLP::bsls::Assert::k_LEVEL_ASSERT)

◆ BSLS_ASSERT_ASSERT_IMP

#define BSLS_ASSERT_ASSERT_IMP (   X,
  LVL 
)
Value:
do { \
BloombergLP::bsls::AssertViolation violation( \
#X, \
LVL); \
BloombergLP::bsls::Assert::invokeHandler(violation); \
} \
} while (false)
#define BSLS_ASSERTIMPUTIL_LINE
Definition bsls_assertimputil.h:166
#define BSLS_ASSERTIMPUTIL_FILE
Definition bsls_assertimputil.h:164
#define BSLS_PERFORMANCEHINT_UNLIKELY_HINT
Definition bsls_performancehint.h:484
#define BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY(expr)
Definition bsls_performancehint.h:452

◆ BSLS_ASSERT_ASSUME_IMP

#define BSLS_ASSERT_ASSUME_IMP (   X,
  LVL 
)    BSLS_ASSERT_DISABLED_IMP(X,LVL)

◆ BSLS_ASSERT_DISABLED_IMP

#define BSLS_ASSERT_DISABLED_IMP (   X,
  LVL 
)

◆ BSLS_ASSERT_INVOKE

#define BSLS_ASSERT_INVOKE (   X)
Value:
do { \
BloombergLP::bsls::AssertViolation violation( \
X, \
BloombergLP::bsls::Assert::k_LEVEL_INVOKE); \
BloombergLP::bsls::Assert::invokeHandler(violation); \
} while (false)

◆ BSLS_ASSERT_INVOKE_NORETURN

#define BSLS_ASSERT_INVOKE_NORETURN (   X)
Value:
do { \
BloombergLP::bsls::AssertViolation violation( \
X, \
BloombergLP::bsls::Assert::k_LEVEL_INVOKE); \
BloombergLP::bsls::Assert::invokeHandlerNoReturn(violation); \
} while (true)

◆ BSLS_ASSERT_IS_ACTIVE

#define BSLS_ASSERT_IS_ACTIVE

◆ BSLS_ASSERT_IS_USED

#define BSLS_ASSERT_IS_USED

◆ BSLS_ASSERT_NO_ASSERTION_MACROS_DEFINED

#define BSLS_ASSERT_NO_ASSERTION_MACROS_DEFINED   1

◆ BSLS_ASSERT_NORETURN_INVOKE_HANDLER

#define BSLS_ASSERT_NORETURN_INVOKE_HANDLER

◆ BSLS_ASSERT_OPT

#define BSLS_ASSERT_OPT (   X)
Value:
X, \
BloombergLP::bsls::Assert::k_LEVEL_OPT)

◆ BSLS_ASSERT_OPT_IS_ACTIVE

#define BSLS_ASSERT_OPT_IS_ACTIVE

◆ BSLS_ASSERT_OPT_IS_USED

#define BSLS_ASSERT_OPT_IS_USED

◆ BSLS_ASSERT_RECURSIVELY_INCLUDED_TESTDRIVER_GUARD

#define BSLS_ASSERT_RECURSIVELY_INCLUDED_TESTDRIVER_GUARD

◆ BSLS_ASSERT_SAFE

#define BSLS_ASSERT_SAFE (   X)
Value:
X, \
BloombergLP::bsls::Assert::k_LEVEL_SAFE)
#define BSLS_ASSERT_DISABLED_IMP(X, LVL)
Definition bsls_assert.h:1713

Typedef Documentation

◆ bdes_Assert

◆ bdes_AssertFailureHandlerGuard

◆ bsls_Assert

◆ bsls_AssertFailureHandlerGuard