diff --git a/Control/CxxUtils/CMakeLists.txt b/Control/CxxUtils/CMakeLists.txt
index e27276167d7d8aa1614b0481988ab88880ed6dd6..a6607bec17365ec01e70de1a94d10918aeb9b1f0 100644
--- a/Control/CxxUtils/CMakeLists.txt
+++ b/Control/CxxUtils/CMakeLists.txt
@@ -107,7 +107,8 @@ foreach( test sincos_test copyif_test ArrayScanner_test Arrayrep_test
       fpcompare_test StrFormat_test
       prefetch_test ClassName_test make_unique_test ones_test
       exctrace1_test bitscan_test ConcurrentRangeMap_test
-      CachedValue_test CachedPointer_test atomic_fetch_minmax_test
+      CachedValue_test CachedPointer_test CachedUniquePtr_test
+      atomic_fetch_minmax_test
       MurmurHash2_test bitmask_test crc64_test Ring_test )
    atlas_add_test( ${test}
       SOURCES test/${test}.cxx
diff --git a/Control/CxxUtils/CxxUtils/CachedUniquePtr.h b/Control/CxxUtils/CxxUtils/CachedUniquePtr.h
new file mode 100644
index 0000000000000000000000000000000000000000..0c83de06ac5c01972454cff0aa88d5fc34e2244e
--- /dev/null
+++ b/Control/CxxUtils/CxxUtils/CachedUniquePtr.h
@@ -0,0 +1,122 @@
+// This file's extension implies that it's C, but it's really -*- C++ -*-.
+/*
+ * Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration.
+ */
+/**
+ * @file CxxUtils/CachedUniquePtr.h
+ * @author scott snyder <snyder@bnl.gov>
+ * @date Mar, 2019
+ * @brief Cached unique_ptr with atomic update.
+ */
+
+
+#ifndef CXXUTILS_CACHEDUNIQUEPTR_H
+#define CXXUTILS_CACHEDUNIQUEPTR_H
+
+
+#include <atomic>
+#include <memory>
+
+
+namespace CxxUtils {
+
+
+/**
+ * @brief Cached pointer with atomic update.
+ *
+ * This acts as a @c unique_ptr that may be set atomically through
+ * a const method.  If the pointer is already set, then the new value
+ * is deleted.  The intended use case is where one maintains some
+ * cached object that is computed lazily.  So one can do, for example,
+ *@code
+ *  CxxUtils::CachedUniquePtr<Payload> m_payload;
+ *  ...
+ *  const Payload* ...::get() const
+ *  {
+ *    if (!m_payload) {
+ *      m_payload.set (std::make_unique<Payload> (...));
+ *    }
+ *    return m_payload.get());
+ *  }
+ @endcode
+ *
+ * This only makes sense if the objects passed to set() are equivalent
+ * in all threads.  This is generally the case for a lazily-computed value,
+ * but this class has no way of enforcing this.
+ *
+ * It is recommended to generally use the template @c CachedUniquePtr, which
+ * is specialized for const types.
+ * The more general template @c CachedUniquePtrT can be used with a non-const
+ * type (but you don't want to do that for the lazy-cache use case).
+ */
+template <class T>
+class CachedUniquePtrT
+{
+public:
+  /// Default constructor.  Sets the element to null.
+  CachedUniquePtrT();
+
+   
+  /// Constructor from an element.
+  CachedUniquePtrT (std::unique_ptr<T> elt);
+
+
+  /// Move constructor.
+  CachedUniquePtrT (CachedUniquePtrT&& other);
+
+
+  /// Move.
+  CachedUniquePtrT& operator= (CachedUniquePtrT&& other);
+
+
+  // Destructor.
+  ~CachedUniquePtrT();
+
+
+  /// Atomically set the element.  If already set, then @c elt is discarded.
+  void set (std::unique_ptr<T> elt) const;
+
+
+  /// Store a new value to the element.
+  /// Not compatible with other concurrent access.
+  void store (std::unique_ptr<T> elt);
+
+
+  /// Return the current value of the element.
+  T* get() const;
+
+
+  /// Dereference the element.
+  T& operator*() const;
+
+
+  /// Dereference the element.
+  T* operator->() const;
+
+
+  /// Test if the element is null.
+  explicit operator bool() const;
+
+
+  /// Transfer ownership from the element: return the current value as a
+  /// unique_ptr, leaving the element null.
+  std::unique_ptr<T> release();
+
+
+private:
+  /// The cached element.
+  mutable std::atomic<T*> m_ptr;
+};
+
+
+template <class T>
+using CachedUniquePtr = CachedUniquePtrT<const T>;
+
+
+} // namespace CxxUtils
+
+
+#include "CxxUtils/CachedUniquePtr.icc"
+
+
+#endif // not CXXUTILS_CACHEDUNIQUEPTR_H
diff --git a/Control/CxxUtils/CxxUtils/CachedUniquePtr.icc b/Control/CxxUtils/CxxUtils/CachedUniquePtr.icc
new file mode 100644
index 0000000000000000000000000000000000000000..f86b4c6293c802935d8669567d1570e4094c0174
--- /dev/null
+++ b/Control/CxxUtils/CxxUtils/CachedUniquePtr.icc
@@ -0,0 +1,172 @@
+// This file's extension implies that it's C, but it's really -*- C++ -*-.
+/*
+ * Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration.
+ */
+// $Id$
+/**
+ * @file CxxUtils/CachedUniquePtr.icc
+ * @author scott snyder <snyder@bnl.gov>
+ * @date Mar, 2019
+ * @brief Cached unique_ptr with atomic update.
+ */
+
+
+namespace CxxUtils {
+
+
+/**
+ * @brief Default constructor.  Sets the element to null.
+ */
+template <class T>
+inline
+CachedUniquePtrT<T>::CachedUniquePtrT()
+  : m_ptr (nullptr)
+{
+}
+
+
+/**
+ * @brief Constructor from an element.
+ */
+template <class T>
+inline
+CachedUniquePtrT<T>::CachedUniquePtrT (std::unique_ptr<T> elt)
+  : m_ptr (elt.release())
+{
+}
+
+
+/**
+ * @brief Move constructor.
+ */
+template <class T>
+inline
+CachedUniquePtrT<T>::CachedUniquePtrT (CachedUniquePtrT&& other)
+  : m_ptr (other.release().release())
+{
+}
+
+
+/**
+ * @brief Move.
+ */
+template <class T>
+inline
+CachedUniquePtrT<T>&
+CachedUniquePtrT<T>::operator= (CachedUniquePtrT&& other)
+{
+  if (this != &other) {
+    store (other.release());
+  }
+  return *this;
+}
+
+
+/** 
+ * @brief Destructor.
+ */
+template <class T>
+inline
+CachedUniquePtrT<T>::~CachedUniquePtrT()
+{
+  delete m_ptr;
+}
+
+
+/**
+ * @brief Atomically set the element.  If already set, then @c elt is discarded.
+ * @param elt The new value for the element.
+ *
+ * If the current value of the element is null, then set it to @c elt.
+ * Otherwise, delete @c elt.
+ * This is done atomically.
+ */
+template <class T>
+inline
+void CachedUniquePtrT<T>::set (std::unique_ptr<T> elt) const
+{
+  // Set the element to ELT if it is currently null.
+  T* ptr = elt.release();
+  T* null = nullptr;
+  if (!m_ptr.compare_exchange_strong (null, ptr)) {
+    // Was already set.  Delete the new value.
+    delete ptr;
+  }
+}
+
+
+/**
+ * @brief Store a new value to the element.
+ *        Not compatible with other concurrent access.
+ */
+template <class T>
+inline
+void CachedUniquePtrT<T>::store (std::unique_ptr<T> elt)
+{
+  T* old = m_ptr.exchange (elt.release());
+  delete old;
+}
+
+
+/**
+ * @brief Return the current value of the element.
+ */
+template <class T>
+inline
+T*
+CachedUniquePtrT<T>::get() const
+{
+  return m_ptr.load();
+}
+
+
+/**
+ * @brief Dereference the element.
+ */
+template <class T>
+inline
+T&
+CachedUniquePtrT<T>::operator*() const
+{
+  return *get();
+}
+
+
+/**
+ * @brief Dereference the element.
+ */
+template <class T>
+inline
+T*
+CachedUniquePtrT<T>::operator->() const
+{
+  return get();
+}
+
+
+/**
+ * @brief Test if the element is null.
+ */
+template <class T>
+inline
+CachedUniquePtrT<T>::operator bool() const
+{
+  return get() != nullptr;
+}
+
+
+/**
+ * @brief Transfer ownership from the element: return the current value as a
+ *        unique_ptr, leaving the element null.
+ */
+template <class T>
+inline
+std::unique_ptr<T>
+CachedUniquePtrT<T>::release()
+{
+  T* old = m_ptr.exchange (nullptr);
+  return std::unique_ptr<T> (old);
+}
+
+
+} // namespace CxxUtils
diff --git a/Control/CxxUtils/share/CachedUniquePtr_test.ref b/Control/CxxUtils/share/CachedUniquePtr_test.ref
new file mode 100644
index 0000000000000000000000000000000000000000..f0c0fd55135d7b4ad32733a58f0c8e26d11d9799
--- /dev/null
+++ b/Control/CxxUtils/share/CachedUniquePtr_test.ref
@@ -0,0 +1,3 @@
+CachedUniquePtr_test
+test1
+test2
diff --git a/Control/CxxUtils/share/ubsan.supp b/Control/CxxUtils/share/ubsan.supp
index 77dc9ed56a865116f1d1452d5a39f82ce5267520..e0ba8742c058c367d432764deccfbb5c5bd8d588 100644
--- a/Control/CxxUtils/share/ubsan.supp
+++ b/Control/CxxUtils/share/ubsan.supp
@@ -9,3 +9,6 @@ vptr_check:PrepRawDataCollection
 # Suppress false positives caused by ToolHandle calls from code
 # generated by cling.
 vptr_check:ToolHandle
+
+# Suppress downcast warnings from boost::any.
+vptr_check:holder
diff --git a/Control/CxxUtils/test/CachedUniquePtr_test.cxx b/Control/CxxUtils/test/CachedUniquePtr_test.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d47894580230d9628af460116af808c9d59510d7
--- /dev/null
+++ b/Control/CxxUtils/test/CachedUniquePtr_test.cxx
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration.
+ */
+
+// $Id$
+/**
+ * @file CxxUtils/test/CachedUniquePtr_test.cxx
+ * @author scott snyder <snyder@bnl.gov>
+ * @date mar, 2019
+ * @brief Unit tests for CachedUniquePtr
+ */
+
+
+#undef NDEBUG
+#include "CxxUtils/CachedUniquePtr.h"
+#include <atomic>
+#include <iostream>
+#include <cassert>
+#include <thread>
+#include <shared_mutex>
+
+
+struct P
+{
+  P(int x) : m_x(x) { ++s_count; }
+  ~P() { --s_count; }
+
+  int m_x;
+  static std::atomic<int> s_count;
+};
+
+std::atomic<int> P::s_count = 0;
+
+
+// Basic tests.
+void test1()
+{
+  std::cout << "test1\n";
+  //int x1 = 0;
+  //int x2 = 0;
+  //int x4 = 0;
+
+  CxxUtils::CachedUniquePtr<P> cp1;
+  assert (!cp1.get());
+  assert (!cp1);
+  assert (P::s_count == 0);
+  cp1.store (std::make_unique<P>(1));
+  assert (P::s_count == 1);
+  assert (cp1.get()->m_x == 1);
+  assert (cp1);
+  assert (cp1->m_x == 1);
+  assert ((*cp1).m_x == 1);
+  cp1.store (nullptr);
+  assert (!cp1.get());
+  assert (P::s_count == 0);
+
+  CxxUtils::CachedUniquePtr<P> cp2 (std::make_unique<P>(2));
+  assert (cp2->m_x == 2);
+  assert (P::s_count == 1);
+
+  cp1 = std::move(cp2);
+  assert (cp1->m_x == 2);
+  assert (!cp2);
+  assert (P::s_count == 1);
+
+  {
+    const CxxUtils::CachedUniquePtr<P> cp3 (std::move (cp1));
+    assert (cp3->m_x == 2);
+    assert (!cp1);
+    assert (P::s_count == 1);
+  }
+  assert (P::s_count == 0);
+
+  cp1.set (std::make_unique<P>(3));
+  assert (cp1->m_x == 3);
+  assert (P::s_count == 1);
+
+  cp1.set (std::make_unique<P>(4));
+  assert (cp1->m_x == 3);
+  assert (P::s_count == 1);
+
+  std::unique_ptr<const P> up = cp1.release();
+  assert (!cp1);
+  assert (up->m_x == 3);
+  assert (P::s_count == 1);
+}
+
+
+class ThreadingTest
+{
+public:
+  static const int NVAL = 10;
+  CxxUtils::CachedUniquePtr<P> m_vals[10];
+  std::shared_timed_mutex m_sm;
+  int m_x[NVAL] = {0};
+
+  void threadedTest();
+
+  struct writerThread
+  {
+    writerThread (ThreadingTest& test, int iworker)
+      : m_test (test), m_iworker (iworker) {}
+    void operator()();
+    ThreadingTest& m_test;
+    int m_iworker;
+  };
+
+  
+  struct readerThread
+  {
+    readerThread (ThreadingTest& test, int iworker)
+      : m_test (test), m_iworker (iworker) {}
+    void operator()();
+    ThreadingTest& m_test;
+    int m_iworker;
+  };
+};
+
+
+void ThreadingTest::writerThread::operator()()
+{
+  int i = m_iworker;
+  do {
+    m_test.m_vals[i].set (std::make_unique<const P>(i));
+    i++;
+    if (i >= NVAL) i = 0;
+  } while (i != m_iworker);
+}
+
+
+void ThreadingTest::readerThread::operator()()
+{
+  bool checked[NVAL] = {false};
+  int nchecked = 0;
+  while (nchecked < NVAL) {
+    int i = m_iworker;
+    do {
+      if (!checked[i] && m_test.m_vals[i]) {
+        assert (m_test.m_vals[i]->m_x == i);
+        checked[i] = true;
+        ++nchecked;
+      }
+      i++;
+      if (i >= NVAL) i = 0;
+    } while (i != m_iworker);
+  }
+}
+
+
+void ThreadingTest::threadedTest()
+{
+  for (int i=0; i < NVAL; i++) {
+    m_vals[i].store (nullptr);
+  }
+
+  const int nthread = 10;
+  std::thread threads[nthread];
+  m_sm.lock();
+
+  for (int i=0; i < nthread; i++) {
+    if (i < 3) {
+      threads[i] = std::thread (writerThread (*this, i));
+    }
+    else {
+      threads[i] = std::thread (readerThread (*this, i));
+    }
+  }
+
+  // Try to get the threads starting as much at the same time as possible.
+  m_sm.unlock();
+  for (int i=0; i < nthread; i++)
+    threads[i].join();
+}
+
+
+// Threading test.
+void test2()
+{
+  std::cout << "test2\n";
+
+  ThreadingTest test;
+#if 0
+  for (int i=0; i < 20; i++)
+    test.threadedTest();
+#endif
+}
+
+
+int main()
+{
+  std::cout << "CachedUniquePtr_test\n";
+  test1();
+  test2();
+  return 0;
+}
diff --git a/Control/CxxUtils/test/ConcurrentBitset_test.cxx b/Control/CxxUtils/test/ConcurrentBitset_test.cxx
index 3b5e985a608f6be69c7eb8fd297bec5f273fe312..8590fc000c5bc31073c7bab2d378de921e804328 100644
--- a/Control/CxxUtils/test/ConcurrentBitset_test.cxx
+++ b/Control/CxxUtils/test/ConcurrentBitset_test.cxx
@@ -17,14 +17,14 @@
 #include "CxxUtils/ConcurrentBitset.h"
 #include "CxxUtils/checker_macros.h"
 #include "TestTools/random.h"
-// Work around a warning in tbb, found by gcc8.
-// Fixed in TBB 2018 U5.
-#if defined(__GNUC__) && __GNUC__ >= 8
+// tbb/machine/gcc_generic.h has spurious trailing semicolons after
+// the clz() functiosn (as of TBB 2019 U1).
+#if defined(__GNUC__)
 # pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wclass-memaccess"
+# pragma GCC diagnostic ignored "-Wpedantic"
 #endif
 #include "tbb/concurrent_unordered_set.h"
-#if defined(__GNUC__) && __GNUC__ >= 8
+#if defined(__GNUC__)
 # pragma GCC diagnostic pop
 #endif
 #include "boost/timer/timer.hpp"