C++ Exception-Safe Assignment Overloading
Implementing C++ assignment overloading can be error-prone if the object involves data on the memory heap.
In this blog post, I would like to quickly discuss how to implement exception safe C++ assignment overloading and how to apply the copy-and-swap idiom to implement it more elegantly.
C++ Assignment Overloading
Exception-safe and self-assignment.
Make sure the resource management inside the assignment overloading has taken throwing exceptions into account to prevent the data loss from the object. In the following example, we strictly follow allocate, populate and deallocate. If we deallocate the cstring member variable before allocate the new buffer, and allocating new buffer throws exception, we lose the cstring data forever.
Valgrind verified that there is no memory leak during runtime.
A more elegant way of implementing assignment overloading is to apply the “copy-and-swap” idiom. We create a public or private noexcept swap member function and use the member function for the temporary copy of the assignment source.
Like this article support the author with.
- 1 Introduction
- 2.1 Exception-Safe and Self-Assignment
- 2.2 Copy-And-Swap Idiom
- Graphics and multimedia
- Language Features
- Unix/Linux programming
- Source Code
- Standard Library
- Tips and Tricks
- Tools and Libraries
- Windows API
- Copy constructors, assignment operators,
Copy constructors, assignment operators, and exception safe assignment
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
How to: Design for exception safety
- 7 contributors
One of the advantages of the exception mechanism is that execution, together with data about the exception, jumps directly from the statement that throws the exception to the first catch statement that handles it. The handler may be any number of levels up in the call stack. Functions that are called between the try statement and the throw statement are not required to know anything about the exception that is thrown. However, they have to be designed so that they can go out of scope "unexpectedly" at any point where an exception might propagate up from below, and do so without leaving behind partially created objects, leaked memory, or data structures that are in unusable states.
A robust exception-handling policy requires careful thought and should be part of the design process. In general, most exceptions are detected and thrown at the lower layers of a software module, but typically these layers do not have enough context to handle the error or expose a message to end users. In the middle layers, functions can catch and rethrow an exception when they have to inspect the exception object, or they have additional useful information to provide for the upper layer that ultimately catches the exception. A function should catch and "swallow" an exception only if it is able to completely recover from it. In many cases, the correct behavior in the middle layers is to let an exception propagate up the call stack. Even at the highest layer, it might be appropriate to let an unhandled exception terminate a program if the exception leaves the program in a state in which its correctness cannot be guaranteed.
No matter how a function handles an exception, to help guarantee that it is "exception-safe," it must be designed according to the following basic rules.
Keep resource classes simple
When you encapsulate manual resource management in classes, use a class that does nothing except manage a single resource. By keeping the class simple, you reduce the risk of introducing resource leaks. Use smart pointers when possible, as shown in the following example. This example is intentionally artificial and simplistic to highlight the differences when shared_ptr is used.
Use the RAII idiom to manage resources
To be exception-safe, a function must ensure that objects that it has allocated by using malloc or new are destroyed, and all resources such as file handles are closed or released even if an exception is thrown. The Resource Acquisition Is Initialization (RAII) idiom ties management of such resources to the lifespan of automatic variables. When a function goes out of scope, either by returning normally or because of an exception, the destructors for all fully-constructed automatic variables are invoked. An RAII wrapper object such as a smart pointer calls the appropriate delete or close function in its destructor. In exception-safe code, it is critically important to pass ownership of each resource immediately to some kind of RAII object. Note that the vector , string , make_shared , fstream , and similar classes handle acquisition of the resource for you. However, unique_ptr and traditional shared_ptr constructions are special because resource acquisition is performed by the user instead of the object; therefore, they count as Resource Release Is Destruction but are questionable as RAII.
The three exception guarantees
Typically, exception safety is discussed in terms of the three exception guarantees that a function can provide: the no-fail guarantee , the strong guarantee , and the basic guarantee .
The no-fail (or, "no-throw") guarantee is the strongest guarantee that a function can provide. It states that the function will not throw an exception or allow one to propagate. However, you cannot reliably provide such a guarantee unless (a) you know that all the functions that this function calls are also no-fail, or (b) you know that any exceptions that are thrown are caught before they reach this function, or (c) you know how to catch and correctly handle all exceptions that might reach this function.
Both the strong guarantee and the basic guarantee rely on the assumption that the destructors are no-fail. All containers and types in the Standard Library guarantee that their destructors do not throw. There is also a converse requirement: The Standard Library requires that user-defined types that are given to it—for example, as template arguments—must have non-throwing destructors.
The strong guarantee states that if a function goes out of scope because of an exception, it will not leak memory and program state will not be modified. A function that provides a strong guarantee is essentially a transaction that has commit or rollback semantics: either it completely succeeds or it has no effect.
The basic guarantee is the weakest of the three. However, it might be the best choice when a strong guarantee is too expensive in memory consumption or in performance. The basic guarantee states that if an exception occurs, no memory is leaked and the object is still in a usable state even though the data might have been modified.
A class can help ensure its own exception safety, even when it is consumed by unsafe functions, by preventing itself from being partially constructed or partially destroyed. If a class constructor exits before completion, then the object is never created and its destructor will never be called. Although automatic variables that are initialized prior to the exception will have their destructors invoked, dynamically allocated memory or resources that are not managed by a smart pointer or similar automatic variable will be leaked.
The built-in types are all no-fail, and the Standard Library types support the basic guarantee at a minimum. Follow these guidelines for any user-defined type that must be exception-safe:
Use smart pointers or other RAII-type wrappers to manage all resources. Avoid resource management functionality in your class destructor, because the destructor will not be invoked if the constructor throws an exception. However, if the class is a dedicated resource manager that controls just one resource, then it's acceptable to use the destructor to manage resources.
Understand that an exception thrown in a base class constructor cannot be swallowed in a derived class constructor. If you want to translate and re-throw the base class exception in a derived constructor, use a function try block.
Consider whether to store all class state in a data member that is wrapped in a smart pointer, especially if a class has a concept of "initialization that is permitted to fail." Although C++ allows for uninitialized data members, it does not support uninitialized or partially initialized class instances. A constructor must either succeed or fail; no object is created if the constructor does not run to completion.
Do not allow any exceptions to escape from a destructor. A basic axiom of C++ is that destructors should never allow an exception to propagate up the call stack. If a destructor must perform a potentially exception-throwing operation, it must do so in a try catch block and swallow the exception. The standard library provides this guarantee on all destructors it defines.
Modern C++ best practices for exceptions and error handling How to: Interface Between Exceptional and Non-Exceptional Code
Submit and view feedback for
CS102: Introduction to Computer Science II
Exception Handling in C++
This page might seem like it duplicates some of what we have just seen, but it is valuable because it gives a different perspective on the topic. Read chapter 1 on pages 15-60.
In Chapter 7 we'll take an in-depth look at the containers in the Standard C++ library, including the stack container. One thing you'll notice is that the declaration of the pop( ) member function looks like this:
You might think it strange that pop( ) doesn't return a value. Instead, it just removes the element at the top of the stack. To retrieve the top value, call top( ) before you call pop( ) . There is an important reason for this behavior, and it has to do with exception safety , a crucial consideration in library design. There are different levels of exception safety, but most importantly, and just as the name implies, exception safety is about correct semantics in the face of exceptions.
Suppose you are implementing a stack with a dynamic array (we'll call it data and the counter integer count ), and you try to write pop( ) so that it returns a value. The code for such a pop( ) might look something like this:
What happens if the copy constructor that is called for the return value in the last line throws an exception when the value is returned? The popped element is not returned because of the exception, and yet count has already been decremented, so the top element you wanted is lost forever! The problem is that this function attempts to do two things at once: (1) return a value, and (2) change the state of the stack. It is better to separate these two actions into two separate member functions, which is exactly what the standard stack class does. (In other words, follow the design practice of cohesion – every function should do one thing well .) Exception-safe code leaves objects in a consistent state and does not leak resources.
You also need to be careful writing custom assignment operators. In Chapter 12 of Volume 1, you saw that operator= should adhere to the following pattern:
- Make sure you're not assigning to self. If you are, go to step 6. (This is strictly an optimization.)
- Allocate new memory required by pointer data members.
- Copy data from the old memory to the new.
- Delete the old memory.
- Update the object's state by assigning the new heap pointers to the pointer data members.
- Return *this .
It's important to not change the state of your object until all the new pieces have been safely allocated and initialized. A good technique is to move steps 2 and 3 into a separate function, often called clone( ) . The following example does this for a class that has two pointer members, theString and theInts :
For convenience, HasPointers uses the MyData class as a handle to the two pointers. Whenever it's time to allocate more memory, whether during construction or assignment, the first clone function is ultimately called to do the job. If memory fails for the first call to the new operator, a bad_alloc exception is thrown automatically. If it happens on the second allocation (for theInts ), we must clean up the memory for theString – hence the first try block that catches a bad_alloc exception. The second try block isn't crucial here because we're just copying int s and pointers (so no exceptions will occur), but whenever you copy objects, their assignment operators can possibly cause an exception, so everything needs to be cleaned up. In both exception handlers, notice that we rethrow the exception. That's because we're just managing resources here; the user still needs to know that something went wrong, so we let the exception propagate up the dynamic chain. Software libraries that don't silently swallow exceptions are called exception neutral . Always strive to write libraries that are both exception safe and exception neutral.
If you inspect the previous code closely, you'll notice that none of the delete operations will throw an exception. This code depends on that fact. Recall that when you call delete on an object, the object's destructor is called. It turns out to be practically impossible to design exception-safe code without assuming that destructors don't throw exceptions. Don't let destructors throw exceptions. (We're going to remind you about this once more before this chapter is done).
C++ Cookbook by D. Ryan Stephens, Christopher Diggins, Jonathan Turkanis, Jeff Cogswell
Get full access to C++ Cookbook and 60K+ other titles, with a free 10-day trial of O'Reilly.
There are also live events, courses curated by job role, and more.
9.5. Safely Copying an Object
You need the basic class copy operations—copy construction and assignment—to be exception-safe.
Employ the tactics discussed in Recipe 9.4 by doing everything that might throw first, then changing the object state with operations that can’t throw only after the hazardous work is complete. Example 9-6 presents the Message class again, this time with the assignment operator and copy constructor defined.
Example 9-6. Exception-safe assignment and copy construction
Get C++ Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.
Don’t leave empty-handed
Get Mark Richards’s Software Architecture Patterns ebook to better understand how to design components—and how they should interact.
It’s yours, free.
Check it out now on O’Reilly
Dive in for free with a 10-day trial of the O’Reilly learning platform—then explore all the other resources our members count on to build skills and solve problems every day.
Exception handling provides a way of transferring control and information from some point in the execution of a program to a handler associated with a point previously passed by the execution (in other words, exception handling transfers control up the call stack).
An exception can be thrown by a throw-expression , dynamic_cast , typeid , new-expression , allocation function , and any of the standard library functions that are specified to throw exceptions to signal certain error conditions (e.g. std::vector::at , std::string::substr , etc).
In order for an exception to be caught, the throw-expression has to be inside a try-block or inside a function called from a try-block, and there has to be a catch clause that matches the type of the exception object.
When declaring a function, the following specification(s) may be provided to limit the types of the exceptions a function may throw:
Errors that arise during exception handling are handled by std::terminate and std::unexpected (until C++17) .
[ edit ] Usage
While throw-expression can be used to transfer control to an arbitrary block of code up the execution stack, for arbitrary reasons (similar to std::longjmp ), its intended usage is error handling.
[ edit ] Error handling
Throwing an exception is used to signal errors from functions, where "errors" are typically limited to only the following    :
- Failures to meet the postconditions, such as failing to produce a valid return value object.
- Failures to meet the preconditions of another function that must be called.
- (for non-private member functions) Failures to (re)establish a class invariant.
In particular, this implies that the failures of constructors (see also RAII ) and most operators should be reported by throwing exceptions.
In addition, so-called wide contract functions use exceptions to indicate unacceptable inputs, for example, std::string::at has no preconditions, but throws an exception to indicate index out of range.
[ edit ] Exception safety
After the error condition is reported by a function, additional guarantees may be provided with regards to the state of the program. The following four levels of exception guarantee are generally recognized    , which are strict supersets of each other:
- Nothrow (or nofail) exception guarantee — the function never throws exceptions. Nothrow (errors are reported by other means or concealed) is expected of destructors and other functions that may be called during stack unwinding. The destructors are noexcept by default. (since C++11) Nofail (the function always succeeds) is expected of swaps, move constructors , and other functions used by those that provide strong exception guarantee.
- Strong exception guarantee — If the function throws an exception, the state of the program is rolled back to the state just before the function call (for example, std::vector::push_back ).
- Basic exception guarantee — If the function throws an exception, the program is in a valid state. No resources are leaked, and all objects' invariants are intact.
- No exception guarantee — If the function throws an exception, the program may not be in a valid state: resource leaks, memory corruption, or other invariant-destroying errors may have occurred.
Generic components may, in addition, offer exception-neutral guarantee : if an exception is thrown from a template parameter (e.g. from the Compare function object of std::sort or from the constructor of T in std::make_shared ), it is propagated, unchanged, to the caller.
[ edit ] Exception objects
While objects of any complete type and cv pointers to void may be thrown as exception objects, all standard library functions throw anonymous temporary objects by value, and the types of those objects are derived (directly or indirectly) from std::exception . User-defined exceptions usually follow this pattern.   
To avoid unnecessary copying of the exception object and object slicing, the best practice for catch clauses is to catch by reference.    
[ edit ] External links
- Recent changes
- Offline version
- What links here
- Related changes
- Upload file
- Special pages
- Printable version
- Permanent link
- Page information
- In other languages
- This page was last modified on 12 September 2023, at 09:35.
- This page has been accessed 238,645 times.
- About cppreference.com
27.9 — Exception specifications and noexcept
(h/t to reader Koe for providing the first draft of this lesson!)
Looking at a typical function declaration, it is not possible to determine whether a function might throw an exception or not:
In the above example, can the doSomething function throw an exception? It’s not clear. But the answer is important in some contexts. If doSomething could throw an exception, then it’s not safe to call this function from a destructor, or any other place where a thrown exception is undesirable.
While comments may help enumerate whether a function throws exceptions or not (and if so, what kind of exceptions), documentation can grow stale and there is no compiler enforcement for comments.
Exception specifications are a language mechanism that was originally designed to document what kind of exceptions a function might throw as part of a function specification. While most of the exception specifications have now been deprecated or removed, one useful exception specification was added as a replacement, which we’ll cover in this lesson.
The noexcept specifier
In C++, all functions are classified as either non-throwing or potentially throwing . A non-throwing function is one that promises not to throw exceptions that are visible to the caller. A potentially throwing function may throw exceptions that are visible to the caller.
To define a function as non-throwing, we can use the noexcept specifier . To do so, we use the noexcept keyword in the function declaration, placed to the right of the function parameter list:
Note that noexcept doesn’t actually prevent the function from throwing exceptions or calling other functions that are potentially throwing. This is allowed so long as the noexcept function catches and handles those exceptions internally, and those exceptions do not exit the noexcept function.
If an unhandled exception would exit a noexcept function, std::terminate will be called (even if there is an exception handler that would otherwise handle such an exception somewhere up the stack). And if std::terminate is called from inside a noexcept function, stack unwinding may or may not occur (depending on implementation and optimizations), which means your objects may or may not be destructed properly prior to termination.
The promise that a noexcept function makes to not throw exceptions that are visible to the caller is a contractual promise, not a promise enforced by the compiler. So while calling a noexcept function should be safe, any exception handling bugs in the noexcept function that cause the contract to be broken will result in termination of the program! This shouldn’t happen, but neither should bugs.
For this reason, it’s best that noexcept functions don’t mess with exceptions at all, or call potentially throwing functions that could raise an exception. A noexcept function can’t have an exception handling bug if no exceptions can possibly be raised in the first place!
Much like functions that differ only in their return values can not be overloaded, functions differing only in their exception specification can not be overloaded.
Illustrating the behavior of noexcept functions and exceptions
The following program illustrates the behavior of noexcept functions and exceptions in various cases:
On the author’s machine, this program printed:
and then the program aborted.
Let’s explore what’s happening here in more detail. Note that tester is a noexcept function, and thus promises not to expose any exception to the caller ( main ).
The first case illustrates that noexcept functions can call potentially throwing functions and even handle any exceptions those functions throw. First, tester(1) is called, which calls potentially throwing function pt , which calls thrower , which throws an exception. The first handler for this exception is in tester , so the exception unwinds the stack (destroying local variable doomed in the process), and the exception is caught and handled within tester . Because tester does not expose this exception to the caller ( main ), there is no violation of noexcept here, and control returns to main .
The second case illustrates what happens when a noexcept function tries to pass an exception back to its caller. First, tester(2) is called, which calls non-throwing function nt , which calls thrower , which throws an exception. The first handler for this exception is in tester . However, nt is noexcept, and to get to the handler in tester , the exception would have to propagate to the caller of nt . That is a violation of the noexcept of nt , and so std::terminate is called, and our program is aborted immediately. On the author’s machine, the stack was not unwound (as illustrated by doomed not being destroyed).
The noexcept specifier with a Boolean parameter
The noexcept specifier has an optional Boolean parameter. noexcept(true) is equivalent to noexcept , meaning the function is non-throwing. noexcept(false) means the function is potentially throwing. These parameters are typically only used in template functions, so that a template function can be dynamically created as non-throwing or potentially throwing based on some parameterized value.
Which functions are non-throwing and potentially-throwing
Functions that are implicitly non-throwing:
Functions that are non-throwing by default for implicitly-declared or defaulted functions:
- Constructors: default, copy, move
- Assignments: copy, move
- Comparison operators (as of C++20)
However, if any of these functions call (explicitly or implicitly) another function which is potentially throwing, then the listed function will be treated as potentially throwing as well. For example, if a class has a data member with a potentially throwing constructor, then the class’s constructors will be treated as potentially throwing as well. As another example, if a copy assignment operator calls a potentially throwing assignment operator, then the copy assignment will be potentially throwing as well.
Functions that are potentially throwing (if not implicitly-declared or defaulted):
- Normal functions
- User-defined constructors
- User-defined operators
The noexcept operator
The noexcept operator can also be used inside expressions. It takes an expression as an argument, and returns true or false if the compiler thinks it will throw an exception or not. The noexcept operator is checked statically at compile-time, and doesn’t actually evaluate the input expression.
The noexcept operator can be used to conditionally execute code depending on whether it is potentially throwing or not. This is required to fulfill certain exception safety guarantees , which we’ll talk about in the next section.
Exception safety guarantees
An exception safety guarantee is a contractual guideline about how functions or classes will behave in the event an exception occurs. There are four levels of exception safety:
- No guarantee -- There are no guarantees about what will happen if an exception is thrown (e.g. a class may be left in an unusable state)
- Basic guarantee -- If an exception is thrown, no memory will be leaked and the object is still usable, but the program may be left in a modified state.
- Strong guarantee -- If an exception is thrown, no memory will be leaked and the program state will not be changed. This means the function must either completely succeed or have no side effects if it fails. This is easy if the failure happens before anything is modified in the first place, but can also be achieved by rolling back any changes so the program is returned to the pre-failure state.
- No throw / No fail -- The function will always succeed (no-fail) or fail without throwing an exception (no-throw).
Let’s look at the no-throw/no-fail guarantees in more detail:
The no-throw guarantee: if a function fails, then it won’t throw an exception. Instead, it will return an error code or ignore the problem. No-throw guarantees are required during stack unwinding when an exception is already being handled; for example, all destructors should have a no-throw guarantee (as should any functions those destructors call). Examples of code that should be no-throw:
- destructors and memory deallocation/cleanup functions
- functions that higher-level no-throw functions need to call
The no-fail guarantee: a function will always succeed in what it tries to do (and thus never has a need to throw an exception, thus, no-fail is a slightly stronger form of no-throw). Examples of code that should be no-fail:
- move constructors and move assignment (move semantics, covered in chapter 22 )
- swap functions
- clear/erase/reset functions on containers
- operations on std::unique_ptr (also covered in chapter 22 )
- functions that higher-level no-fail functions need to call
When to use noexcept
Just because your code doesn’t explicitly throw any exceptions doesn’t mean you should start sprinkling noexcept around your code. By default, most functions are potentially throwing, so if your function calls other functions, there is a good chance it calls a function that is potentially throwing, and thus is potentially throwing too.
There are a few good reasons to mark functions a non-throwing:
- Non-throwing functions can be safely called from functions that are not exception-safe, such as destructors
- Functions that are noexcept can enable the compiler to perform some optimizations that would not otherwise be available. Because a noexcept function cannot throw an exception outside the function, the compiler doesn’t have to worry about keeping the runtime stack in an unwindable state, which can allow it to produce faster code.
- There are significant cases where knowing a function is noexcept allows us to produce more efficient implementations in our own code: the standard library containers (such as std::vector ) are noexcept aware and will use the noexcept operator to determine whether to use move semantics (faster) or copy semantics (slower) in some places. We cover move semantics in chapter 22 , and this optimization in lesson 27.10 -- std::move_if_noexcept .
The standard library’s policy is to use noexcept only on functions that must not throw or fail. Functions that are potentially throwing but do not actually throw exceptions (due to implementation) typically are not marked as noexcept .
For your code, there are two places that make sense to use noexcept :
- On constructors and overloaded assignment operators that are no-throw (to take advantage of optimizations).
- On functions for which you want to express a no-throw or no-fail guarantee (e.g. to document that they can be safely called from destructors or other noexcept functions)
Make constructors and overloaded assignment operators noexcept when you can. Use noexcept on other functions to express a no-fail or no-throw guarantee.
If you are uncertain whether a function should have a no-fail/no-throw guarantee, err on the side of caution and do not mark it with noexcept . Reversing a decision to use noexcept violates an interface commitment to the user about the behavior of the function, and may break existing code. Making guarantees stronger by later adding noexcept to a function that was not originally noexcept is considered safe.
Dynamic exception specifications
Before C++11, and until C++17, dynamic exception specifications were used in place of noexcept . The dynamic exception specifications syntax uses the throw keyword to list which exception types a function might directly or indirectly throw:
Due to factors such as incomplete compiler implementations, some incompatibility with template functions, common misunderstandings about how they worked, and the fact that the standard library mostly didn’t use them, the dynamic exception specifications were deprecated in C++11 and removed from the language in C++17 and C++20. See this paper for more context.
- ACCU Online
- ACCU 2023 Bristol
- ACCU 2022 Bristol
- ACCU 2021 Online
- ACCU Autumn 2019 Belfast
- ACCU 2019 Bristol
- ACCU 2018 Bristol
- ACCU 2017 Bristol
- ACCU 2016 Bristol
- ACCU 2015 Bristol
- All Conferences
- Journals Overview
- Overload by Issue
- Overload by Article
- Overload by Author
- Overload by Cover
- C Vu by Issue
- C Vu by Article
- C Vu by Author
- C Vu by Cover
- Reviews Overview
- Reviews by Date
- Reviews by Title
- Reviews by Author
- Reviews by Reviewer
- Reviews by Publisher
- Reviews by Rating
- Local Groups
Designing C++ Interfaces - Exception Safety
By Mark Radford
Overload, 9(43):, June 2001
In software design, there is so much which can be achieved without considering the implementation language, and therefore which can be expressed in a modelling language - UML for example - usually with textual augmentation. However, this only goes so far, and when it comes to specifics, the implementation language exerts a great influence on how the design of an interface is approached.
The term Modern C++ refers to the C++ of the ISO standard, which has been around now for almost three years, and this is a language which has moved on dramatically from the C++ described in older texts, for example the ARM [ E-S ]. The evolutionary factors which influence interface design fall into two categories:
New features introduced into the language during the standardisation process, the dominant examples being exception safety and templates.
The experience gained over the years bringing about maturity in the ideas on how interface design is approached, an example of this being the role played by freestanding functions.
This article concentrates on exception safety, presenting some examples of how writing exception safe code can seriously affect the design of participating components.
A Simple Example
It is a mistake to think the presence of exceptions only affects implementation - nothing could be further from the truth. Exceptions - at least in the form they take in C++ - impact on the design of every interface!
By way of an illustrative example, consider the stack class design shown in the following fragment:
The above approach is typical of how a stack might have been designed in days past, and is indeed reminiscent of a design presented by Carroll & Ellis (in [ Carroll-1995 ], page 112). Now, consider one typical way in which this might be used:
The problem is this: if an exception is thrown, for example during the assignment, the element on the top of the stack will be lost forever, and with this interface there is no other way around this problem - an alternative approach is required.
Consider an alternative design, and a typical way of using it (analogous to the one shown above), in which the query and pop operations are separated (as in the stack adapter class of the C++ standard library):
What makes this example particularly relevant is that it illustrates how, in the presence of exceptions, we need to beware of preconceived ideas when designing interfaces. With the latter approach, if the assignment throws, the state of the stack is not affected, but although it now looks like we have a solution, appearances can be deceptive and there is a price to pay: separating the top() and pop() functions means the design is not thread safe.
To illustrate what can go wrong in a multithreaded environment, consider the example in the following fragment:
If, following #1, another thread makes a call to pop() , the value read in #2 may not - and indeed is unlikely to be - the one expected. The pop() and top() calls need to be placed in a synchronised piece of code (i.e. either critical sectioned or mutexed).
Exception Safety Guarantees
So far, this article has discussed exception safety without clarifying what that means. It is time to set the record straight. The meaning of the term exception safety is crystallised in the following guarantees (originally formulated by Dave Abrahams):
The basic guarantee - if an operation throws an exception, no resources will leak as a result and invariants (of the component on which the operation is invoked) are preserved
The strong guarantee - additionally, the program's state remains unchanged
The nothrow guarantee - an operation guarantees never to propagate an exception under any circumstances
The basic and strong guarantees represent the two levels of exception safety. The purpose of the nothrow guarantee is to enable the basic and strong guarantees to be honoured.
Normally, it is the strong guarantee which requires operations promising nothrow, except that destructors must always honour this guarantee (because the destructor might be called as the result of an exception being thrown, with highly undesirable results).
The basic guarantee is relatively straightforward to honour: provided resources are managed using the "Execute Around Object" [ Henney2000 ] idiom (a.k.a. "Resource Acquisition is Initialisation" [ Stroustrup1997 ]) - this way, the C++ practice of placing finalisation code in destructors ensures that whichever route an operation exits by, cleanup will be performed.
Honouring the strong guarantee is a bit trickier, and more often requires the assistance of the nothrow guarantee...
The Role of the Nothrow Guarantee
To illustrate the role of the nothrow guarantee making it possible to honour the strong exception safety guarantee, consider the case of inserting elements into std::vector , as in the following code fragment:
Inserting into containers can result in an exception being thrown in two ways:
bad_alloc might be thrown if there is a need to increase the vector's capacity and the attempt to allocate memory fails, and
the generic nature of the code means we must assume it is possible for type's copy constructor might throw an exception.
It is the second of these which causes the problem, because an exception might be thrown when some but not all insertions have already completed, v is left in an indeterminate state.
The procedure for manipulating containers with exception safety in C++ forms a simple and recurring pattern: create a copy of the original, manipulate the working copy, then swap the working copy back into the original, as illustrated in the next code fragment:
The new state of v - that is, the state following the insertions - is constructed in the working copy v_temp . No operations have been applied to v at this point, therefore if at any time during the insertions into v_temp , an exception is thrown, the control flow exits from f_safe leaving the state of v unchanged. Next, vector::swap() is invoked, swapping over the respective contents of v and v_temp ; the working copy v_temp now holds the original state, and is destructed in due course.
This procedure is possible because - and only because - swap() supports the nothrow guarantee, so the transition in v 's state can be made without any danger of an exception being thrown.
This whole procedure is rather long winded and inefficient - there is the overhead of copying the vector. This is necessary because the nature of vector is such that it is not possible to perform an insert without the danger that an exception might be thrown.
If the container in the above example is changed to list then the code becomes:
It is noteworthy that node based containers such as list, can offer the capability to insert elements with a guarantee that the operation will either succeed or will leave the container unchanged (that is, the strong guarantee is supported), because the insert operation can construct the new nodes, and then splice them in. Construction of the new nodes takes place before any operations are applied to the original, so if an exception is thrown during this process the state of the original is not affected. Splicing in the newly constructed nodes simply involves reassignment of pointers - an operation which will not throw, i.e. the operation carries a nothrow guarantee.
If the strong guarantee is to be supported, then the additional performance overhead caused by the fact that some containers must be copied when inserting elements, becomes a factor which must be considered when choosing a container.
The understanding that exception safety affects the design of interfaces is a major step towards achieving exception safety - an assertion which hopefully this article has gone some way towards substantiating by means of the examples and explanations presented.
For example, to achieve exception safety it was necessary to modify the interface of the stack class, although in doing so thread safety must become an issue for the client code (a design tradeoff - you can't have your cake and eat it).
Another example was that of inserting elements into a vector, where supporting the strong exception safety guarantee was possible only because vector's interface offers a swap() function supporting the nothrow guarantee. This was then contrasted with node based containers such as list, which can offer an insert() operation which naturally supports the strong guarantee.
Writing exception safe code in C++ seemed very difficult at one time, but now seems less so largely because the techniques for doing so - as well as the tradeoffs involved - are much better understood within the community.
[Carroll-1995] Martin D Carroll and Margaret A Ellis, " Designing and Coding Reusable C++ ", Addison-Wesley, 1995.
[E-S] Margaret A. Ellis and Bjarne Stroustrup, " The Annotated C++ Reference Manual ", Addison-Wesley 1990.
[Henney2000] Kevlin Hennery, " C++ Patterns: Executing Around Sequences " ( www.curbralan.com )
[Stroustrup1997] Bjarne Stroustrup, " C++ Programming Language ", 3rd edition, Addison-Wesley, 1997.
Copyright (c) 2018-2023 ACCU; all rights reserved.
Template by Bootstrapious .
Ported to Hugo by DevCows .
Customized for ACCU by Jim and Bob.
Hosting provided by Bytemark .
By clicking "Share IP Address" you agree ACCU can forward your IP address to third-party sites to enhance the information presented on the site, and that these sites may store cookies on your device.
- Latest Articles
- Top Articles
- Posting/Update Guidelines
- Article Help Forum
- View Unanswered Questions
- View All Questions
- View C# questions
- View C++ questions
- View Python questions
- View Java questions
- CodeProject.AI Server
- All Message Boards...
- Running a Business
- Sales / Marketing
- Collaboration / Beta Testing
- Work Issues
- Design and Architecture
- Artificial Intelligence
- Internet of Things
- ATL / WTL / STL
- Managed C++/CLI
- Objective-C and Swift
- System Admin
- Hosting and Servers
- Linux Programming
- .NET (Core and Framework)
- Visual Basic
- Web Development
- Site Bugs / Suggestions
- Spam and Abuse Watch
- The Insider Newsletter
- The Daily Build Newsletter
- Newsletter archive
- CodeProject Stuff
- Most Valuable Professionals
- The Lounge
- The CodeProject Blog
- Where I Am: Member Photos
- The Insider News
- The Weird & The Wonderful
- What is 'CodeProject'?
- General FAQ
- Ask a Question
- Bugs and Suggestions
Exception Safe Assignment
Longer title: exception safe assignment operator of resource owning objects . Uff. Because the object owns a resource, how do we write an exception safe assignment operator which will have to free up the old and allocate the new resource. By exception safe, I don’t mean that it will never throw, that’s not possible. Instead, I mean safe in the sense that it either succeeds OR in case of exceptions, the state of assigned to object is exactly as it was prior to the assignment. Like this:
If assignment operator...
...throws an exception, we want the state of...
...to be as it was in line #3 .
The trick is two fold:
- a copy constructor is needed, and
- noexcept swap function. Like this:
Here, the copy constructor allocates the new resource first, then copies its content; the swap function just swaps pointers to the resources, which is always a noexcept operation. Having implemented a copy constructor and swap function, we can now implement every assignment operator to have a strong exception guarantee like this:
Here’s how it works: we first make a temporary copy, which does the resource allocation. At this stage, exceptions can be thrown, but we have not yet modified the assigned to object. Only after the resource allocation succeeds do we perform the noexcept swap. The destructor of your temporary object will take care of cleaning up the currently owned resource (that’s RAII at its best).
Complete listing ( assignment.cpp ):
This article, along with any associated source code and files, is licensed under The MIT License
Comments and Discussions
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.
Exception-Safe Class Design, Part 1: Copy Assignment Difficulty: 7 / 10
Is it possible to make any C++ class strongly exception-safe, for example for its copy assignment operator? If so, how? What are the issues and consequences?
1. What are the three common levels of exception safety? Briefly explain each one and why it is important.
2. What is the canonical form of strongly exception-safe copy assignment?
3. Consider the following class:
Assume that any T1 or T2 operation might throw. Without changing the structure of the class, is it possible to write a strongly exception-safe Widget::operator=( const Widget& )? Why or why not? Draw conclusions.
4. Describe and demonstrate a simple transformation that works on any class in order to make strongly exception-safe copy assignment possible and easy for that class. Where have we seen this transformation technique before in other contexts? Cite GotW issue number(s).
Review: Exception Safety Canonical Forms
The canonical Abrahams Guarantees are as follows.
1. Basic Guarantee: If an exception is thrown, no resources are leaked, and objects remain in a destructible and usable -- but not necessarily predictable -- state. This is the weakest usable level of exception safety, and is appropriate where client code can cope with failed operations that have already made changes to objects' state.
2. Strong Guarantee: If an exception is thrown, program state remains unchanged. This level always implies global commit-or-rollback semantics, including that no references or iterators into a container be invalidated if an operation fails.
In addition, certain functions must provide an even stricter guarantee in order to make the above exception safety levels possible:
3. Nothrow Guarantee: The function will not emit an exception under any circumstances. It turns out that it is sometimes impossible to implement the strong or even the basic guarantee unless certain functions are guaranteed not to throw (e.g., destructors, deallocation functions). As we will see below, an important feature of the standard auto_ptr is that no auto_ptr operation will throw.
It involves two steps: First, provide a nonthrowing Swap() function that swaps the guts (state) of two objects:
Second, implement operator=() using the "create a temporary and swap" idiom:
The Cargill Widget Example
This brings us to the Guru questions, starting with a new exception safety challenge proposed by Tom Cargill:
Short answer: In general, no, it can't be done without changing the structure of Widget.
In the Example 3 case, it's not possible to write a strongly exception-safe Widget::operator=() because there's no way that we can change the state of both of the t1_ and t2_ members atomically. Say that we attempt to change t1_, then attempt to change t2_. The problem is twofold:
1. If the attempt to change t1_ throws, t1_ must be unchanged. That is, to make Widget::operator=() strongly exception-safe relies fundamentally on the exception safety guarantees provided by T1, namely that T1::operator=() (or whatever mutating function we are using) either succeeds or does not change its target. This comes close to requiring the strong guarantee of T1::operator=(). (The same reasoning applies to T2::operator=().)
2. If the attempt to change t1_ succeeds, but the attempt to change t2_ throws, we've entered a "halfway" state and cannot in general roll back the change already made to t1_.
Therefore, the way Widget is currently structured, its operator=() cannot be made strongly exception-safe.
Our goal: To write a Widget::operator=() that is strongly exception-safe, without making any assumptions about the exception safety of any T1 or T2 operation. Can it be done? Or is all lost?
A Complete Solution: Using the Pimpl Idiom
The good news is that, even though Widget::operator=() can't be made strongly exception-safe without changing Widget's structure, the following simple transformation always works:
The way to solve the problem is hold the member objects by pointer instead of by value, preferably all behind a single pointer with a Pimpl transformation (described in GotW issues like 7 , 15 , 24 , 25 , and 28 ).
Here is the canonical Pimpl exception-safety transformation:
Now we can easily implement a nonthrowing Swap(), which means we can easily implement exception-safe copy assignment: First, provide the nonthrowing Swap() function that swaps the guts (state) of two objects (note that this function can provide the nothrow guarantee because no auto_ptr operations are permitted to throw exceptions):
Second, implement the canonical form of operator=() using the "create a temporary and swap" idiom:
A Potential Objection, and Why It's Unreasonable
Some may object: "Aha! Therefore this proves exception safety is unattainable in general, because you can't solve the general problem of making any arbitrary class strongly exception-safe without changing the class!"
Such a position is unreasonable and untenable. The Pimpl transformation, a minor structural change, IS the solution to the general problem. To say, "no, you can't do that, you have to be able to make an arbitrary class exception-safe without any changes," is unreasonable for the same reason that "you have to be able to make an arbitrary class meet New Requirement #47 without any changes" is unreasonable.
Unreasonable Statement #1: "Polymorphism doesn't work in C++ because you can't make an arbitrary class usable in place of a Base& without changing it (to derive from Base)."
Unreasonable Statement #2: "STL containers don't work in C++ because you can't make an arbitrary class usable in an STL container without changing it (to provide an assignment operator)."
Unreasonable Statement #3: "Exception safety doesn't work in C++ because you can't make an arbitrary class strongly exception-safe without changing it (to put the internals in a Pimpl class)."
Clearly all the above arguments are equally bankrupt, and the Pimpl transformation is indeed the general solution to strongly exception-safe objects.
So, what have we learned?
Conclusion 1: Exception Safety Affects a Class's Design
Exception safety is never "just an implementation detail." The Pimpl transformation is a minor structural change, but still a change. GotW #8 shows another example of how exception safety considerations can affect the design of a class's member functions.
Conclusion 2: You Can Always Make Your Code (Nearly) Strongly Exception-Safe
There's an important principle here:
Just because a class you use isn't in the least exception-safe is no reason that YOUR code that uses it can't be (nearly) strongly exception-safe.
Anybody can use a class that lacks a strongly exception-safe copy assignment operator and make that use exception-safe. The "hide the details behind a pointer" technique can be done equally well by either the Widget implementor or the Widget user... it's just that if it's done by the implementor it's always safe, and the user won't have to do this:
Conclusion 3: Use Pointers Judiciously
To quote Scott Meyers:
"When I give talks on EH, I teach people two things: "- POINTERS ARE YOUR ENEMIES, because they lead to the kinds of problems that auto_ptr is designed to eliminate.
To wit, bald pointers should normally be owned by manager objects that own the pointed-at resource and perform automatic cleanup. Then Scott continues:
"- POINTERS ARE YOUR FRIENDS, because operations on pointers can't throw. "Then I tell them to have a nice day :-)
Scott captures a fundamental dichotomy well. Fortunately, in practice you can and should get the best of both worlds:
- USE POINTERS BECAUSE THEY ARE YOUR FRIENDS, because operations on pointers can't throw.
- KEEP THEM FRIENDLY BY WRAPPING THEM IN MANAGER OBJECTS like auto_ptrs, because this guarantees cleanup. This doesn't compromise the nonthrowing advantages of pointers because auto_ptr operations never throw either (and you can always get at the real pointer inside an auto_ptr whenever you need to).