BDE 4.14.0 Production release
|
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. | |
Provide build-specific, runtime-configurable assertion macros.
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).
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.
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).
Depending on the build, assertion macros can expand in 3 different ways:
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.
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:
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.
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.
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.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:
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:
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:
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:
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.
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).
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).
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.
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").
Prior to the introduction of bsls::AssertViolation
, the signature for bsls::Assert::ViolationHandler
was this:
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
.
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:
Now consider a client that uses this setWidth
method:
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
:
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.
To recap, there are three (mutually compatible) general build targets:
BDE_BUILD_TARGET_OPT
BDE_BUILD_TARGET_SAFE
BDE_BUILD_TARGET_SAFE_2
seven (mutually exclusive) component-specific assertion levels:BSLS_ASSERT_LEVEL_ASSERT_SAFE
BSLS_ASSERT_LEVEL_ASSERT
BSLS_ASSERT_LEVEL_ASSERT_OPT
BSLS_ASSERT_LEVEL_NONE
BSLS_ASSERT_LEVEL_ASSUME_OPT
BSLS_ASSERT_LEVEL_ASSUME_ASSERT
BSLS_ASSERT_LEVEL_ASSUME_SAFE
and four (mutually exclusive) component-specific review levels:BSLS_REVIEW_LEVEL_REVIEW_SAFE
BSLS_REVIEW_LEVEL_REVIEW
BSLS_REVIEW_LEVEL_REVIEW_OPT
BSLS_REVIEW_LEVEL_NONE
The above macros can be defined (externally) by the build environment to affect which of the three assert macros:BSLS_ASSERT_SAFE(boolean-valued expression)
BSLS_ASSERT(boolean-valued expression)
BSLS_ASSERT_OPT(boolean-valued expression)
will be enabled in assert mode, which will be in review mode, which will be assumed, and which will be disabled.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:
IS_ACTIVE
: Defined if the corresponding level is enabled in assert or review mode. For example, BSLS_ASSERT_SAFE_IS_ACTIVE
is defined if (and only if) the conditions expressed using BSLS_ASSERT_SAFE
will be checked at runtime (either as assertions or reviews).IS_REVIEW
: Defined if the corresponding level is enabled in review mode.IS_ASSUMED
: Defined if the corresponding level is assumed.IS_USED
: Defined if assert expressions for the corresponding level need to be valid (i.e., if they are "ODR-used"). For example, BSLS_ASSERT_SAFE_IS_USED
is defined if (and only if) the conditions expressed using BSLS_ASSERT_SAFE
will be compiled. Note that this is a super-set of the cases where BSLS_ASSERT_SAFE_IS_ACTIVE
will be defined, which is when the conditions will be checked at runtime, while BSLS_ASSERT_SAFE_IS_USED
is also defined if the conditions are assumed or if BSLS_ASSERT_VALIDATE_DISABLED_MACROS
is defined.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:
BSLS_ASSERT_SAFE_IS_ACTIVE
BSLS_ASSERT_IS_ACTIVE
BSLS_ASSERT_OPT_IS_ACTIVE
These three are defined if the corresponding macro is in review mode - and thus the expression will be checked and the review violation handler will be invoked on failure. These will be defined when the review level has been set to a level higher than the assert level:BSLS_ASSERT_SAFE_IS_REVIEW
BSLS_ASSERT_IS_REVIEW
BSLS_ASSERT_OPT_IS_REVIEW
These three are defined if the corresponding macro is being assumed, and it will be hard undefined behavior to violate these expressions:BSLS_ASSERT_SAFE_IS_ASSUMED
BSLS_ASSERT_IS_ASSUMED
BSLS_ASSERT_OPT_IS_ASSUMED
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.
BSLS_ASSERT_SAFE_IS_USED
BSLS_ASSERT_IS_USED
BSLS_ASSERT_OPT_IS_USED
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 – 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; }
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:
Now, the ComplexObject::isPurplish
function will only exist in a subset of builds:
BSLS_ASSERT_SAFE
assertions are enabled in assert or review mode, the function will be compiled and invoked.BSLS_ASSERT_VALIDATE_DISABLED_MACROS
is defined the function will be compiled. This will make sure that a future change does not invalidate the implementation of isPurplish()
even though it is not used.BSLS_ASSERT_SAFE
assertions are assumed the function will be compiled and might be invoked, or at least have its implementation inspected by the compiler to improve code generation.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:
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:
#define BDE_ASSERT_CPP | ( | X | ) | BSLS_ASSERT(X) |
#define BDE_ASSERT_H | ( | X | ) | BSLS_ASSERT_SAFE(X) |
#define BSL_ASSERT_CPP | ( | X | ) | BSLS_ASSERT(X) |
#define BSL_ASSERT_H | ( | X | ) | BSLS_ASSERT_SAFE(X) |
#define BSLS_ASSERT | ( | X | ) |
#define BSLS_ASSERT_ASSERT | ( | X | ) |
#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_INVOKE | ( | X | ) |
#define BSLS_ASSERT_INVOKE_NORETURN | ( | X | ) |
#define BSLS_ASSERT_IS_ACTIVE |
#define BSLS_ASSERT_IS_USED |
#define BSLS_ASSERT_NO_ASSERTION_MACROS_DEFINED 1 |
#define BSLS_ASSERT_NORETURN_INVOKE_HANDLER |
#define BSLS_ASSERT_OPT | ( | X | ) |
#define BSLS_ASSERT_OPT_IS_ACTIVE |
#define BSLS_ASSERT_OPT_IS_USED |
#define BSLS_ASSERT_RECURSIVELY_INCLUDED_TESTDRIVER_GUARD |
#define BSLS_ASSERT_SAFE | ( | X | ) |
typedef bsls::Assert bdes_Assert |
typedef bsls::Assert bsls_Assert |