diff --git a/Modules/Core/Common/include/itkMetaDataDictionary.h b/Modules/Core/Common/include/itkMetaDataDictionary.h index b909ca7fd5a..1303a9bd081 100644 --- a/Modules/Core/Common/include/itkMetaDataDictionary.h +++ b/Modules/Core/Common/include/itkMetaDataDictionary.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace itk { @@ -35,6 +36,15 @@ namespace itk * classes, is designed to provide a mechanism for storing a collection of * arbitrary data types. The main motivation for such a collection is to * associate arbitrary data elements with itk DataObjects. + * + * The MetaDataDictionary implements shallow copying with copy on + * write behavior. When a copy of this class is created, the new copy + * will be shared with the old copy via C++11 shared pointers. When a + * non-constant operation is done, if the dictionary is not unique to + * this object, then a deep copy is performed. This make is very cheap + * to create multiple copies of the same dictionary if they are never + * modified. + * * \ingroup ITKCommon */ class ITKCommon_EXPORT MetaDataDictionary @@ -57,8 +67,10 @@ class ITKCommon_EXPORT MetaDataDictionary MetaDataDictionary(); // Copy Constructor MetaDataDictionary(const MetaDataDictionary &); + MetaDataDictionary(MetaDataDictionary &&) = default; // operator = MetaDataDictionary & operator=(const MetaDataDictionary &); + MetaDataDictionary & operator=(MetaDataDictionary &&) = default; // Destructor virtual ~MetaDataDictionary(); @@ -74,6 +86,9 @@ class ITKCommon_EXPORT MetaDataDictionary // API. The implementation will be in the DLL. MetaDataObjectBase::Pointer & operator[](const std::string &); + // \brief Get a constant point to a DataObject + // + // If the key does not exist then nullptr is returned. const MetaDataObjectBase * operator[](const std::string &) const; const MetaDataObjectBase * Get(const std::string &) const; @@ -112,8 +127,18 @@ class ITKCommon_EXPORT MetaDataDictionary /** remove all MetaObjects from dictionary */ void Clear(); + void Swap( MetaDataDictionary &other ); + private: - MetaDataDictionaryMapType *m_Dictionary; + bool MakeUnique(void); + + std::shared_ptr m_Dictionary; }; + +inline void swap(MetaDataDictionary &a, MetaDataDictionary &b ) +{ + a.Swap(b); +} + } #endif // itkMetaDataDictionary_h diff --git a/Modules/Core/Common/include/itkMetaDataObject.h b/Modules/Core/Common/include/itkMetaDataObject.h index 16b1763ec43..aad02728843 100644 --- a/Modules/Core/Common/include/itkMetaDataObject.h +++ b/Modules/Core/Common/include/itkMetaDataObject.h @@ -170,13 +170,13 @@ inline void EncapsulateMetaData(MetaDataDictionary & Dictionary, const char *key template< typename T > inline bool ExposeMetaData(const MetaDataDictionary & Dictionary, const std::string key, T & outval) { - if ( !Dictionary.HasKey(key) ) + auto keyIter = Dictionary.Find(key); + if ( keyIter == Dictionary.End() ) { return false; } - const MetaDataObjectBase::ConstPointer baseObjectSmartPointer = Dictionary[key]; - auto const * const TempMetaDataObject = dynamic_cast< MetaDataObject< T > const * >( baseObjectSmartPointer.GetPointer() ); + auto const * const TempMetaDataObject = dynamic_cast< MetaDataObject< T > const * >( keyIter->second.GetPointer() ); if ( TempMetaDataObject == nullptr ) { return false; diff --git a/Modules/Core/Common/include/itkObject.h b/Modules/Core/Common/include/itkObject.h index 3691c272013..9861fadcd74 100644 --- a/Modules/Core/Common/include/itkObject.h +++ b/Modules/Core/Common/include/itkObject.h @@ -172,6 +172,7 @@ class ITKCommon_EXPORT Object:public LightObject * Set the MetaDataDictionary */ void SetMetaDataDictionary(const MetaDataDictionary & rhs); + void SetMetaDataDictionary( MetaDataDictionary && rrhs); /** * A facility to help application programmers set a diff --git a/Modules/Core/Common/src/itkMetaDataDictionary.cxx b/Modules/Core/Common/src/itkMetaDataDictionary.cxx index 997d61d949a..ebb108cdb86 100644 --- a/Modules/Core/Common/src/itkMetaDataDictionary.cxx +++ b/Modules/Core/Common/src/itkMetaDataDictionary.cxx @@ -21,22 +21,20 @@ namespace itk { MetaDataDictionary ::MetaDataDictionary() + : m_Dictionary( std::make_shared() ) { - m_Dictionary = new MetaDataDictionaryMapType; } MetaDataDictionary ::~MetaDataDictionary() { - delete m_Dictionary; - m_Dictionary = nullptr; } MetaDataDictionary ::MetaDataDictionary(const MetaDataDictionary & old) + // perform shallow copy, so m_Dictionary is shared + : m_Dictionary( old.m_Dictionary ) { - m_Dictionary = new MetaDataDictionaryMapType; - *m_Dictionary = *( old.m_Dictionary ); } MetaDataDictionary & MetaDataDictionary @@ -44,7 +42,8 @@ ::operator=(const MetaDataDictionary & old) { if(this != &old) { - *m_Dictionary = *( old.m_Dictionary ); + // perform shallow copy, so m_Dictionary is shared + m_Dictionary = old.m_Dictionary; } return *this; } @@ -53,6 +52,7 @@ void MetaDataDictionary ::Print(std::ostream & os) const { + os << "Dictionary use_count: " << m_Dictionary.use_count() << std::endl; for ( MetaDataDictionaryMapType::const_iterator it = m_Dictionary->begin(); it != m_Dictionary->end(); ++it ) @@ -60,12 +60,14 @@ ::Print(std::ostream & os) const os << ( *it ).first << " "; ( *it ).second->Print(os); } + } MetaDataObjectBase::Pointer & MetaDataDictionary ::operator[](const std::string & key) { + MakeUnique(); return ( *m_Dictionary )[key]; } @@ -73,9 +75,13 @@ const MetaDataObjectBase * MetaDataDictionary ::operator[](const std::string & key) const { - MetaDataObjectBase::Pointer entry = ( *m_Dictionary )[key]; - const MetaDataObjectBase * constentry = entry.GetPointer(); + auto iter = m_Dictionary->find(key); + if (iter == m_Dictionary->end()) + { + return nullptr; + } + const MetaDataObjectBase * constentry = iter->second.GetPointer(); return constentry; } @@ -96,6 +102,7 @@ void MetaDataDictionary ::Set(const std::string & key, MetaDataObjectBase * object) { + MakeUnique(); (*m_Dictionary)[key] = object; } @@ -126,6 +133,7 @@ MetaDataDictionary::Iterator MetaDataDictionary ::Begin() { + MakeUnique(); return m_Dictionary->begin(); } @@ -140,6 +148,7 @@ MetaDataDictionary::Iterator MetaDataDictionary ::End() { + MakeUnique(); return m_Dictionary->end(); } @@ -154,6 +163,7 @@ MetaDataDictionary::Iterator MetaDataDictionary ::Find(const std::string & key) { + MakeUnique(); return m_Dictionary->find(key); } @@ -168,7 +178,30 @@ void MetaDataDictionary ::Clear() { - this->m_Dictionary->clear(); + // Construct a new one instead of enforcing uniqueness then clearing + this->m_Dictionary = std::make_shared(); +} + +void +MetaDataDictionary +::Swap( MetaDataDictionary &other ) +{ + using std::swap; + swap(m_Dictionary, other.m_Dictionary); +} + + +bool +MetaDataDictionary +::MakeUnique() +{ + if (m_Dictionary.use_count() > 1) + { + // copy the shared dictionary. + m_Dictionary = std::make_shared(*m_Dictionary); + return true; + } + return false; } bool @@ -180,6 +213,11 @@ ::Erase( const std::string& key ) if( it != end ) { + if (MakeUnique()) + { + // Need to find the correct iterator, in the new copy + it = m_Dictionary->find( key ); + } m_Dictionary->erase( it ); return true; } diff --git a/Modules/Core/Common/src/itkObject.cxx b/Modules/Core/Common/src/itkObject.cxx index c7ccc0a6513..262dafaefb0 100644 --- a/Modules/Core/Common/src/itkObject.cxx +++ b/Modules/Core/Common/src/itkObject.cxx @@ -654,8 +654,22 @@ ::SetMetaDataDictionary(const MetaDataDictionary & rhs) { if ( m_MetaDataDictionary == nullptr ) { - m_MetaDataDictionary = new MetaDataDictionary; + m_MetaDataDictionary = new MetaDataDictionary(rhs); + return; } *m_MetaDataDictionary = rhs; } + +void +Object +::SetMetaDataDictionary( MetaDataDictionary && rrhs) +{ + if ( m_MetaDataDictionary == nullptr ) + { + m_MetaDataDictionary = new MetaDataDictionary(std::move(rrhs)); + return; + } + *m_MetaDataDictionary = std::move(rrhs); +} + } // end namespace itk diff --git a/Modules/Core/Common/test/CMakeLists.txt b/Modules/Core/Common/test/CMakeLists.txt index e55cd7fe245..684c5c1ab8e 100644 --- a/Modules/Core/Common/test/CMakeLists.txt +++ b/Modules/Core/Common/test/CMakeLists.txt @@ -634,5 +634,6 @@ set(ITKCommonGTests itkShapedImageNeighborhoodRangeGTest.cxx itkSmartPointerGTest.cxx itkCommonTypeTraitsGTest.cxx + itkMetaDataDictionaryGTest.cxx ) CreateGoogleTestDriver(ITKCommon "${ITKCommon-Test_LIBRARIES}" "${ITKCommonGTests}") diff --git a/Modules/Core/Common/test/itkMetaDataDictionaryGTest.cxx b/Modules/Core/Common/test/itkMetaDataDictionaryGTest.cxx new file mode 100644 index 00000000000..2974c087d26 --- /dev/null +++ b/Modules/Core/Common/test/itkMetaDataDictionaryGTest.cxx @@ -0,0 +1,259 @@ +/*========================================================================= + * + * Copyright Insight Software Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#include "itkGTest.h" + +#include "itkMetaDataDictionary.h" +#include "itkMetaDataObject.h" + +#include + +namespace +{ + +itk::MetaDataDictionary createMetaDataDictionary( void ) +{ + itk::MetaDataDictionary metaDataDictionary; + + itk::EncapsulateMetaData( metaDataDictionary, "one", static_cast(1)); + itk::EncapsulateMetaData( metaDataDictionary, "two", static_cast(2)); + + using ObjectType = itk::LightObject; + using PointerType = typename ObjectType::Pointer; + PointerType obj = ObjectType::New(); + itk::EncapsulateMetaData( metaDataDictionary, "object", obj); + + return metaDataDictionary; +} + +template +itk::MetaDataObjectBase::Pointer createMetaDataObject(const T &invalue) +{ + typename itk::MetaDataObject< T >::Pointer temp = itk::MetaDataObject< T >::New(); + temp->SetMetaDataObjectValue(invalue); + return temp; +} +} + +TEST(MetaDataDictionary, Basic) +{ + // This test exercises and checks the non-constant interface + itk::MetaDataDictionary dic = createMetaDataDictionary(); + + EXPECT_TRUE(dic.HasKey("one")); + EXPECT_TRUE(dic.HasKey("two")); + EXPECT_TRUE(dic.HasKey("object")); + + EXPECT_FALSE(dic.HasKey("three")); + EXPECT_FALSE(dic.HasKey("")); + EXPECT_FALSE(dic.HasKey("ONE")); + EXPECT_FALSE(dic.HasKey("SomethingElseThatDoesNotExist")); + + EXPECT_EQ(dic.GetKeys().size(), 3u); + + + EXPECT_NE(dic.Get("one"), nullptr); + EXPECT_NE(dic.Get("two"), nullptr); + EXPECT_NE(dic.Get("object"), nullptr); + EXPECT_THROW(dic.Get("three"), itk::ExceptionObject); + EXPECT_THROW(dic.Get(""), itk::ExceptionObject); + EXPECT_THROW(dic.Get("ONE"), itk::ExceptionObject); + + EXPECT_EQ( dic.GetKeys().size(), 3u); + + EXPECT_EQ( std::distance(dic.Begin(), dic.End()), 3u); + + + auto iter = dic.Find("object"); + + EXPECT_NE(iter, dic.End()); + EXPECT_EQ(iter->first, "object"); + + iter = dic.Find("nothing"); + EXPECT_EQ(iter, dic.End()); + + EXPECT_FALSE(dic.Erase("One")); + EXPECT_EQ(std::distance(dic.Begin(), dic.End()), 3u); + EXPECT_EQ(dic.GetKeys().size(), 3u); + EXPECT_TRUE(dic.HasKey("one") ); + + + EXPECT_TRUE(dic.Erase("two")); + EXPECT_EQ(std::distance(dic.Begin(), dic.End()), 2u); + EXPECT_EQ(dic.GetKeys().size(), 2u); + EXPECT_FALSE(dic.HasKey("two")); + + dic.Clear(); + EXPECT_EQ(std::distance(dic.Begin(), dic.End()), 0u); + EXPECT_EQ(dic.GetKeys().size(), 0u); + EXPECT_FALSE(dic.HasKey("one")); + EXPECT_FALSE(dic.HasKey("two")); + EXPECT_FALSE(dic.HasKey("object")); + + // move assignment + dic = createMetaDataDictionary(); + + EXPECT_TRUE(dic.HasKey("one")); + EXPECT_TRUE(dic.HasKey("two")); + EXPECT_TRUE(dic.HasKey("object")); + EXPECT_EQ(dic.GetKeys().size(), 3u); + + + EXPECT_EQ(dic["nothing"], nullptr); + EXPECT_EQ(dic.GetKeys().size(), 4u); + EXPECT_EQ(std::distance(dic.Begin(), dic.End()), 4u); + EXPECT_TRUE(dic.HasKey("nothing")); + + +} + +TEST(MetaDataDictionary, ConstBasic) +{ + // This test exercises and checks the constant interface + const itk::MetaDataDictionary cdic = createMetaDataDictionary(); + + EXPECT_TRUE(cdic.HasKey("one")); + EXPECT_TRUE(cdic.HasKey("two")); + EXPECT_TRUE(cdic.HasKey("object")); + + EXPECT_FALSE(cdic.HasKey("three")); + EXPECT_FALSE(cdic.HasKey("")); + EXPECT_FALSE(cdic.HasKey("ONE")); + EXPECT_FALSE(cdic.HasKey("SomethingElseThatDoesNotExist")); + + EXPECT_EQ( cdic.GetKeys().size(), 3u); + + EXPECT_NE(cdic.Get("one"), nullptr); + EXPECT_NE(cdic.Get("two"), nullptr); + EXPECT_NE(cdic.Get("object"), nullptr); + EXPECT_THROW(cdic.Get("three"), itk::ExceptionObject); + EXPECT_THROW(cdic.Get(""), itk::ExceptionObject); + EXPECT_THROW(cdic.Get("ONE"), itk::ExceptionObject); + + EXPECT_EQ(cdic.GetKeys().size(), 3u); + + EXPECT_EQ(std::distance(cdic.Begin(), cdic.End()), 3u); + + + auto iter = cdic.Find("object"); + + EXPECT_NE(iter, cdic.End()); + EXPECT_EQ(iter->first, "object"); + + iter = cdic.Find("nothing"); + EXPECT_EQ(iter, cdic.End()); + + EXPECT_EQ(cdic["nothing"], nullptr); + EXPECT_EQ(cdic.GetKeys().size(), 3u); + EXPECT_EQ(std::distance(cdic.Begin(), cdic.End()), 3u); + + + float f = -99; + itk::ExposeMetaData(cdic,"one",f); + EXPECT_EQ(f, 1.0); + itk::ExposeMetaData(cdic,"two",f); + EXPECT_EQ(f, 2.0); + + itk::LightObject::Pointer objPtr; + itk::ExposeMetaData(cdic, "object", objPtr); + EXPECT_FALSE(objPtr.IsNull()); +} + + +TEST(MetaDataDictionary, CopyOnWrite) +{ + + { + itk::MetaDataDictionary dic = createMetaDataDictionary(); + const itk::MetaDataDictionary dic_copy = dic; + + // The use_count is not exposed in the interface, but it is in the + // print method. + std::cout << "The use_count for the std::map in the dictionary should be 2." << std::endl; + dic.Print(std::cout); + + dic["one"] = createMetaDataObject(11.0f); + + float f = -99; + itk::ExposeMetaData(dic,"one",f); + EXPECT_EQ(f, 11.0f); + itk::ExposeMetaData(dic_copy,"one",f); + EXPECT_EQ(f, 1.0f); + + } + + { + itk::MetaDataDictionary dic = createMetaDataDictionary(); + const itk::MetaDataDictionary dic_copy = dic; + + dic.Set("two",createMetaDataObject(22.0f)); + + float f = -99; + itk::ExposeMetaData(dic,"two",f); + EXPECT_EQ(f, 22.0f); + itk::ExposeMetaData(dic_copy,"two",f); + EXPECT_EQ(f, 2.0f); + + } + + { + itk::MetaDataDictionary dic = createMetaDataDictionary(); + const itk::MetaDataDictionary dic_copy = dic; + + dic.Set("three",createMetaDataObject(3.0f)); + + float f = -99; + itk::ExposeMetaData(dic,"three",f); + EXPECT_EQ(f, 3.0f); + EXPECT_FALSE(dic_copy.HasKey("three")); + } + + { + itk::MetaDataDictionary dic = createMetaDataDictionary(); + const itk::MetaDataDictionary dic_copy = dic; + + dic.Erase("two"); + + EXPECT_FALSE(dic.HasKey("two")); + EXPECT_TRUE(dic_copy.HasKey("two")); + } + + { + itk::MetaDataDictionary dic = createMetaDataDictionary(); + const itk::MetaDataDictionary dic_copy = dic; + + dic.Clear(); + + EXPECT_FALSE(dic.HasKey("one")); + EXPECT_FALSE(dic.HasKey("two")); + EXPECT_FALSE(dic.HasKey("object")); + EXPECT_EQ(dic.GetKeys().size(), 0u); + + EXPECT_TRUE(dic_copy.HasKey("one")); + EXPECT_TRUE(dic_copy.HasKey("two")); + EXPECT_TRUE(dic_copy.HasKey("object")); + EXPECT_EQ(dic_copy.GetKeys().size(), 3u); + + float f = -99; + itk::ExposeMetaData(dic_copy,"one",f); + EXPECT_EQ(f, 1.0f); + + } + + +}