), followed by the assignment operators. .. java.lang.Exception: I'm outta here! Now j = 1. That is, the left-hand operand forgetIt() of the operator / throws an exception before the right-hand operand is evaluated and its embedded assignment of 2 to j. Expressions that include postfix or prefix increment (++), postfix or prefix decrement (--), or compound assignment operators always result in compound operations. The Java Language Specification also permits reads and writes of 64-bit values to be non-atomic (see rule VNA05-J. Ensure atomicity when reading and.">
Skip to content

Exception Safe Assignment Operator Java

Before we move into discussing move semantics (the next step on my quest to convert you all to teaching in C++14), let’s clear up something that I often see being taught in a subtly “wrong” way with respect to memory management in even C++98.

Consider the following class, which might be written by a student in a fundamental data structures course:

What we see here is standard fare: we have a class that utilizes dynamic memory (via the pointer), and thus is provides the “Big Three”: a copy constructor, an assignment operator, and a destructor. These three overloads give us the value semantics that we desire, making it so that our class can “blend in” with the built in types of the language and standard library. C++ is rooted in value semantics, so it’s crucial that we get this right so that our classes are well behaved.

But let’s look more closely at our assignment operator. You may have seen it written something like this:

where is a helper function for releasing the memory associated with the current object, and is another helper function that is responsible for creating the memory for a new independent copy of the parameter, assigning it into the current object.

There are actually a couple of problems with this approach.

Pedagogical Complaints

Because the language semantics are often taught very early on in the course, and (at least at UIUC) to fairly inexperienced programmers (through no fault of their own: it’s just their second programming experience in the curriculum in its current form), you have to dance around this issue of the “self assignment” check.

: an enigma for “green” students

is particularly nuanced for students still struggling to understand some of the fundamental differences between Java and C++ (or C and C++). This requires them to understand:

  • the type of (a pointer to the current instance)

  • the purpose of as getting a pointer to the argument (not a reference, and understanding that itself is not a pointer)

  • what it would mean if .

That’s quite a bit of information we’re expecting them to digest in just a short little snippet. But if you write your assignment operator this way, it’s such a critical moment: if they forget this check, they will have memory errors coming out of their ears.

Technical Complaints

However, that’s not the real meat of my argument. My real beef with this setup is that it is completely exception unsafe. And, unless you’re living in a fairytale world where you

  • never allocate to the free store, and
  • never use the standard library

ignoring exceptions will be a fatal mistake at some point in your experience with C++.

And, please don’t come to me claiming that your codebase “doesn’t throw exceptions”, because you and I both know that’s just a load of crock. =)

Nearly every useful program is going to at least do one (and, likely, both) of the above two things. This means you have to care about exceptions.

Exception Safety

So what’s “unsafe” about this code?

Patience, young padawan. Let’s take a step back.

First, let’s identify where exceptions could be thrown, and then define what we want our class to do in the event of an exception. This will define what kind of “exception safety guarantee” we want to provide.

One of the bullet points I made above (when I was being rude; sorry) was that the memory allocator can throw exceptions. How could that be the case? Let’s look at three fairly simple examples:

  • We’re out of memory. This causes a exception to be thrown from the call to that we’ll be using to allocate our array.

  • A constructor for an element in the array throws during the call.

  • The assignment operator for an element in the new array throws when we are copying the data.

So clearly, then, the line that invokes has the potential to throw an exception. What would happen to our data structure in this case? There are a few cases:

  • It could be completely empty if the allocation itself fails (out of memory or a throwing constructor for during the call).

  • It could be partially-copied if the exception came from when copying the data.

So what can we do to deal with this exception?

Let me be clear here: our goal is not to handle the exception. What should the program do if it can no longer allocate heap memory, for example? That’s not something that our data structure should be deciding. So we’re not even going to try to catch and handle this error: instead, what we’re going to try to guarantee is something about the state of our class after the exception has been thrown—namely, that it is in the same state as it was before the assignment line that caused the exception.

Putting the safety back on our assignment operator

Using the template we had before, we could imagine rewriting it in the following way:

Shield your eyes! The horror! The code has exploded, has a horrible block to handle the fact that could throw during the assignment into the array (the cost of a generic type here), and is now almost certainly above the threshold of beginner programmers.

But it is exception safe.

Back to the drawing board

The above monstrosity is clearly beyond what we want to teach. There’s no reason we shouldn’t be able to achieve both goals: the ease of understanding that came with the then version, and also providing the strong exception safety guarantee.

This is where the “copy and swap” idiom comes into play. (It’s worth noting that this idiom is even more useful in C++11/14, but we’ll get there later.)

We start with the following observations:

  • We want to create new memory that is a completely independent copy of the parameter.

  • We must release the memory associated with the current object.

  • The current object must refer to the new memory that we’ve created.

…what if I told you that we already wrote most of this by virtue of having a well defined class? A helper? We have a copy constructor! Let’s see if we can’t use that as a form of “helper function”. Remember from the above code that we want the following chain of events:

  • Allocate memory
  • Copy over values
  • Free old memory
  • Refer to the new copy

and further note that there’s no reason we couldn’t do the last two in a different order (we’d just need a temporary).

Let’s first define a helper function that we’ll use in our implementation:

To be a good citizen, let’s also define a non-member swap function for our class that just delegates to the helper above:

And now consider the following implementation for the assignment operator:

Woah! We have two lines of code. There’s no way that gets us everything we need… right?

But it does.

  • We get the copy by virtue of the argument being passed by value.

  • If the copying fails (e.g., the copy constructor throws), our function is not run at all, so our class appears unchanged by the assignment because it truly didn’t happen.

  • Swapping with the value parameter accomplishes our resource release. Remember that any parameters passed by value are going to have their destructors invoked when the stack frame for that function is popped.

  • We don’t have to check for self assignment anymore, as that code path can now be handled without a special case. Yes, it is less efficient, but the point of checking for self assignment wasn’t as an optimization, it was a “please don’t crash my program” check.

The Catch

The only thing we have to guarantee now is that our copy constructor satisfies the basic exception guarantee (which is to say that it does not leak in the event of an exception), which isn’t too bad (though the code is still not ideal):

The nastiness here is because the marked line (1) could throw during ’s assignment operator.

In the general case, there are ways of avoiding the here, but I think this is a reasonable compromise for now. It’s worth noting at this point that if you were teaching with the then style, your copy constructor was probably exception unsafe, too, so this isn’t just a reflection of some “complication” in the copy-and-swap idiom.

If you’re dealing with some type that you know does not throw from its assignment operator (an assumption I’m willing to make when teaching novice programmers), then the code can be simplified to just:

We’ll revisit this later when we start talking about C++11/14 and show how just a simple language switch can ensure that we get the basic exception guarantee out of our copy constructor in the general case by only a one line change to the above initializer list!

Closing Thoughts: An exception-safe “Big Three” for intro programmers

Let’s recap what our code for the “Big Three” looks like now, including all of our helper functions:

Advantages

  • Real world applicability: exceptions are everwhere, you need to know them and how to handle them

  • Simplified explanation for , using language concepts they’re learning as they are doing copy constructors anyway (pass by value)

  • Elimination of the self assignment check (self assignment is automatically valid in the copy-and-swap idiom)

  • A helper function that’s useful to the outside world:

Disadvantages

  • Requires some discussion of what exceptions are, what they are used for, and why we care about them

  • If you are truly being careful, in C++98/03 you will need to have a block in the copy constructor (but not in C++11/14, more to come…)

Coming Up

Now that we know about the copy-and-swap idiom, in the next post I’m going to talk briefly about move semantics in C++11/14, and then we can move on to tackle what I teased at in the very first post in this series: that we can teach manual memory management in C++11/14 without losing out on any teaching opportunities compared to C++98/03, all the while simultaneously being more modern and encouraging students to write safe code.

Yell at me in the comments!

Compound operations are operations that consist of more than one discrete operation. Expressions that include postfix or prefix increment (), postfix or prefix decrement (), or compound assignment operators always result in compound operations. Compound assignment expressions use operators such as , , , , , , , , and [JLS 2015]. Compound operations on shared variables must be performed atomically to prevent data races and race conditions.

For information about the atomicity of a grouping of calls to independently atomic methods that belong to thread-safe classes, see VNA03-J. Do not assume that a group of calls to independently atomic methods is atomic.

The Java Language Specification also permits reads and writes of 64-bit values to be non-atomic (see rule VNA05-J. Ensure atomicity when reading and writing 64-bit values).

Noncompliant Code Example (Logical Negation)

This noncompliant code example declares a shared variable and provides a method that negates the current value of :

Execution of this code may result in a data race because the value of is read, negated, and written back.

Consider, for example, two threads that call . The expected effect of toggling twice is that it is restored to its original value. However, the following scenario leaves in the incorrect state:

Time

flag=

Thread

Action

1

true

t1

Reads the current value of , true, into a temporary variable

2

true

t2

Reads the current value of , (still) true, into a temporary variable

3

true

t1

Toggles the temporary variable to false

4

true

t2

Toggles the temporary variable to false

5

false

t1

Writes the temporary variable's value to

6

false

t2

Writes the temporary variable's value to

As a result, the effect of the call by t2 is not reflected in ; the program behaves as if was called only once, not twice.

Noncompliant Code Example (Bitwise Negation)

The method may also use the compound assignment operator to negate the current value of :

This code is also not thread-safe. A data race exists because is a non-atomic compound operation.

Noncompliant Code Example (Volatile)

Declaring volatile also fails to solve the problem:

This code remains unsuitable for multithreaded use because declaring a variable volatile fails to guarantee the atomicity of compound operations on the variable.

Compliant Solution (Synchronization)

This compliant solution declares both the and methods as synchronized:

This solution guards reads and writes to the field with a lock on the instance, that is, . Furthermore, synchronization ensures that changes are visible to all threads. Now, only two execution orders are possible, one of which is shown in the following scenario:

Time

flag=

Thread

Action

1

true

t1

Reads the current value of , true, into a temporary variable

2

true

t1

Toggles the temporary variable to false

3

false

t1

Writes the temporary variable's value to

4

false

t2

Reads the current value of , false, into a temporary variable

5

false

t2

Toggles the temporary variable to true

6

true

t2

Writes the temporary variable's value to

The second execution order involves the same operations, but t2 starts and finishes before t1.
Compliance with LCK00-J. Use private final lock objects to synchronize classes that may interact with untrusted code can reduce the likelihood of misuse by ensuring that untrusted callers cannot access the lock object.

Compliant Solution (Volatile-Read, Synchronized-Write)

In this compliant solution, the method is not synchronized, and is declared as volatile. This solution is compliant because the read of in the method is an atomic operation and the volatile qualification assures visibility. The method still requires synchronization because it performs a non-atomic operation.

This approach must not be used for getter methods that perform any additional operations other than returning the value of a volatile field without use of synchronization. Unless read performance is critical, this technique may lack significant advantages over synchronization [Goetz 2006].

Compliant Solution (Read-Write Lock)

This compliant solution uses a read-write lock to ensure atomicity and visibility:

Read-write locks allow shared state to be accessed by multiple readers or a single writer but never both. According to Goetz [Goetz 2006]:

In practice, read-write locks can improve performance for frequently accessed read-mostly data structures on multiprocessor systems; under other conditions they perform slightly worse than exclusive locks due to their greater complexity.

Profiling the application can determine the suitability of read-write locks.

Compliant Solution ()

This compliant solution declares to be of type :

The variable is updated using the method of the class. All updates are visible to other threads.

Noncompliant Code Example (Addition of Primitives)

In this noncompliant code example, multiple threads can invoke the method to set the and fields. Because this class fails to test for integer overflow, users of the class must ensure that the arguments to the method can be added without overflow (see NUM00-J. Detect or prevent integer overflow for more information).

The method contains a race condition. For example, when and currently have the values and , respectively, and one thread calls while another calls , the method might return either or , or it might overflow. Overflow will occur when the first thread reads and after the second thread has set the value of to but before it has set the value of to .

Note that declaring the variables as volatile fails to resolve the issue because these compound operations involve reads and writes of multiple variables.

Noncompliant Code Example (Addition of Atomic Integers)

In this noncompliant code example, and are replaced with atomic integers:

The simple replacement of the two fields with atomic integers fails to eliminate the race condition because the compound operation is still non-atomic.

Compliant Solution (Addition)

This compliant solution synchronizes the and methods to ensure atomicity:

The operations within the synchronized methods are now atomic with respect to other synchronized methods that lock on that object's monitor (that is, its intrinsic lock). It is now possible, for example, to add overflow checking to the synchronized method without introducing the possibility of a race condition.

Risk Assessment

When operations on shared variables are not atomic, unexpected results can be produced. For example, information can be disclosed inadvertently because one user can receive information about other users.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

VNA02-J

Medium

Probable

Medium

P8

L2

Automated Detection

Some available static analysis tools can detect the instances of non-atomic update of a concurrently shared value. The result of the update is determined by the interleaving of thread execution. These tools can detect the instances where thread-shared data is accessed without holding an appropriate lock, possibly causing a race condition.

ToolVersionCheckerDescription
CodeSonar4.2FB.MT_CORRECTNESS.IS2_INCONSISTENT_SYNC
FB.MT_CORRECTNESS.IS_FIELD_NOT_GUARDED
FB.MT_CORRECTNESS.STCAL_INVOKE_ON_STATIC_CALENDAR_INSTANCE
FB.MT_CORRECTNESS.STCAL_INVOKE_ON_STATIC_DATE_FORMAT_INSTANCE
FB.MT_CORRECTNESS.STCAL_STATIC_CALENDAR_INSTANCE
FB.MT_CORRECTNESS.STCAL_STATIC_SIMPLE_DATE_FORMAT_INSTANCE
Inconsistent synchronization
Field not guarded against concurrent access
Call to static Calendar
Call to static DateFormat
Static Calendar field
Static DateFormat
Coverity7.5

GUARDED_BY_VIOLATION
INDIRECT_GUARDED_BY_VIOLATION
NON_STATIC_GUARDING_STATIC
NON_STATIC_GUARDING_STATIC
SERVLET_ATOMICITY
FB.IS2_INCONSISTENT_SYNC
FB.IS_FIELD_NOT_GUARDED
FB.IS_INCONSISTENT_SYNC
FB.STCAL_INVOKE_ON_STATIC_ CALENDAR_INSTANCE
FB.STCAL_INVOKE_ON_STATIC_ DATE_FORMAT_INSTANCE
FB.STCAL_STATIC_CALENDAR_ INSTANCE
FB.STCAL_STATIC_SIMPLE_DATE_ FORMAT_INSTANCE

Implemented
Parasoft Jtest 10.3 TRS.SSUG, TRS.MRAVImplemented
ThreadSafe1.3

CCE_SL_INCONSISTENT
CCE_CC_CALLBACK_ACCESS
CCE_SL_MIXED
CCE_SL_INCONSISTENT_COL
CCE_SL_MIXED_COL
CCE_CC_UNSAFE_CONTENT

Implemented

Related Guidelines

Bibliography


final class Flag { private boolean flag = true; public void toggle() { // Unsafe flag = !flag; } public boolean getFlag() { // Unsafe return flag; } }
final class Flag { private boolean flag = true; public void toggle() { // Unsafe flag ^= true; // Same as flag = !flag; } public boolean getFlag() { // Unsafe return flag; } }
final class Flag { private volatile boolean flag = true; public void toggle() { // Unsafe flag ^= true; } public boolean getFlag() { // Safe return flag; } }
final class Flag { private boolean flag = true; public synchronized void toggle() { flag ^= true; // Same as flag = !flag; } public synchronized boolean getFlag() { return flag; } }
final class Flag { private volatile boolean flag = true; public synchronized void toggle() { flag ^= true; // Same as flag = !flag; } public boolean getFlag() { return flag; } }
final class Flag { private boolean flag = true; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); public void toggle() { writeLock.lock(); try { flag ^= true; // Same as flag = !flag; } finally { writeLock.unlock(); } } public boolean getFlag() { readLock.lock(); try { return flag; } finally { readLock.unlock(); } } }
import java.util.concurrent.atomic.AtomicBoolean; final class Flag { private AtomicBoolean flag = new AtomicBoolean(true); public void toggle() { boolean temp; do { temp = flag.get(); } while (!flag.compareAndSet(temp, !temp)); } public AtomicBoolean getFlag() { return flag; } }
final class Adder { private int a; private int b; public int getSum() { return a + b; } public void setValues(int a, int b) { this.a = a; this.b = b; } }
final class Adder { private final AtomicInteger a = new AtomicInteger(); private final AtomicInteger b = new AtomicInteger(); public int getSum() { return a.get() + b.get(); } public void setValues(int a, int b) { this.a.set(a); this.b.set(b); } }
final class Adder { private int a; private int b; public synchronized int getSum() { // Check for overflow return a + b; } public synchronized void setValues(int a, int b) { this.a = a; this.b = b; } }