diff options
| author | Nat Goodspeed <nat@lindenlab.com> | 2016-10-12 23:01:48 -0400 | 
|---|---|---|
| committer | Nat Goodspeed <nat@lindenlab.com> | 2016-10-12 23:01:48 -0400 | 
| commit | 763509589dc9933d04a26e8109f90591eef1d012 (patch) | |
| tree | b74614fa4933e02ecee3f0f6466dd4799cead0e7 | |
| parent | 704c53b3c506c1274981b3e1ca5a22c16e4fbbb4 (diff) | |
MAINT-5232: Add LLHeteroMap to contain objects of unrelated classes.
| -rw-r--r-- | indra/llcommon/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | indra/llcommon/llheteromap.cpp | 32 | ||||
| -rw-r--r-- | indra/llcommon/llheteromap.h | 95 | ||||
| -rw-r--r-- | indra/llcommon/tests/llheteromap_test.cpp | 158 | 
4 files changed, 288 insertions, 0 deletions
| diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 99514fdbf7..d9ab7c1b38 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -68,6 +68,7 @@ set(llcommon_SOURCE_FILES      llformat.cpp      llframetimer.cpp      llheartbeat.cpp +    llheteromap.cpp      llinitparam.cpp      llinitdestroyclass.cpp      llinstancetracker.cpp @@ -173,6 +174,7 @@ set(llcommon_HEADER_FILES      llhandle.h      llhash.h      llheartbeat.h +    llheteromap.h      llindexedvector.h      llinitdestroyclass.h      llinitparam.h @@ -337,6 +339,7 @@ if (LL_TESTS)    LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}") +  LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}")  ## llexception_test.cpp isn't a regression test, and doesn't need to be run  ## every build. It's to help a developer make implementation choices about diff --git a/indra/llcommon/llheteromap.cpp b/indra/llcommon/llheteromap.cpp new file mode 100644 index 0000000000..7c19196e0c --- /dev/null +++ b/indra/llcommon/llheteromap.cpp @@ -0,0 +1,32 @@ +/** + * @file   llheteromap.cpp + * @author Nat Goodspeed + * @date   2016-10-12 + * @brief  Implementation for llheteromap. + *  + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Copyright (c) 2016, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llheteromap.h" +// STL headers +// std headers +// external library headers +// other Linden headers + +LLHeteroMap::~LLHeteroMap() +{ +    // For each entry in our map, we must call its deleter, which is the only +    // record we have of its original type. +    for (TypeMap::iterator mi(mMap.begin()), me(mMap.end()); mi != me; ++mi) +    { +        // mi->second is the std::pair; mi->second.first is the void*; +        // mi->second.second points to the deleter function +        (mi->second.second)(mi->second.first); +        mi->second.first = NULL; +    } +} diff --git a/indra/llcommon/llheteromap.h b/indra/llcommon/llheteromap.h new file mode 100644 index 0000000000..9d6f303d08 --- /dev/null +++ b/indra/llcommon/llheteromap.h @@ -0,0 +1,95 @@ +/** + * @file   llheteromap.h + * @author Nat Goodspeed + * @date   2016-10-12 + * @brief  Map capable of storing objects of diverse types, looked up by type. + *  + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Copyright (c) 2016, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLHETEROMAP_H) +#define LL_LLHETEROMAP_H + +#include <typeinfo> +#include <utility>                  // std::pair +#include <map> + +/** + * LLHeteroMap addresses an odd requirement. Usually when you want to put + * objects of different classes into a runtime collection of any kind, you + * derive them all from a common base class and store pointers to that common + * base class. + * + * LLInitParam::BaseBlock uses disturbing raw-pointer arithmetic to find data + * members in its subclasses. It seems that no BaseBlock subclass can be + * stored in a polymorphic class of any kind: the presence of a vtbl pointer + * in the layout silently throws off the reinterpret_cast arithmetic. Bad + * Things result. (Many thanks to Nicky D for this analysis!) + * + * LLHeteroMap collects objects WITHOUT a common base class, retrieves them by + * object type and destroys them when the LLHeteroMap is destroyed. + */ +class LLHeteroMap +{ +public: +    ~LLHeteroMap(); + +    /// find or create +    template <class T> +    T& obtain() +    { +        // Look up map entry by typeid(T). We don't simply use mMap[typeid(T)] +        // because that requires default-constructing T on every lookup. For +        // some kinds of T, that could be expensive. +        TypeMap::iterator found = mMap.find(&typeid(T)); +        if (found == mMap.end()) +        { +            // Didn't find typeid(T). Create an entry. Because we're storing +            // only a void* in the map, discarding type information, make sure +            // we capture that type information in our deleter. +            void* ptr = new T(); +            void (*dlfn)(void*) = &deleter<T>; +            std::pair<TypeMap::iterator, bool> inserted = +                mMap.insert(TypeMap::value_type(&typeid(T), +                    TypeMap::mapped_type(ptr, dlfn))); +            // Okay, now that we have an entry, claim we found it. +            found = inserted.first; +        } +        // found->second is the std::pair; second.first is the void* +        // pointer to the object in question. Cast it to correct type and +        // dereference it. +        return *(static_cast<T*>(found->second.first)); +    } + +private: +    template <class T> +    static +    void deleter(void* p) +    { +        delete static_cast<T*>(p); +    } + +    // Comparing two std::type_info* values is tricky, because the standard +    // does not guarantee that there will be only one type_info instance for a +    // given type. In other words, &typeid(A) in one part of the program may +    // not always equal &typeid(A) in some other part. Use special comparator. +    struct type_info_ptr_comp +    { +        bool operator()(const std::type_info* lhs, const std::type_info* rhs) +        { +            return lhs->before(*rhs); +        } +    }; + +    // What we actually store is a map from std::type_info (permitting lookup +    // by object type) to a void* pointer to the object PLUS its deleter. +    typedef std::map< +        const std::type_info*, std::pair<void*, void (*)(void*)>, +        type_info_ptr_comp> +    TypeMap; +    TypeMap mMap; +}; + +#endif /* ! defined(LL_LLHETEROMAP_H) */ diff --git a/indra/llcommon/tests/llheteromap_test.cpp b/indra/llcommon/tests/llheteromap_test.cpp new file mode 100644 index 0000000000..4f38933e50 --- /dev/null +++ b/indra/llcommon/tests/llheteromap_test.cpp @@ -0,0 +1,158 @@ +/** + * @file   llheteromap_test.cpp + * @author Nat Goodspeed + * @date   2016-10-12 + * @brief  Test for llheteromap. + *  + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Copyright (c) 2016, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llheteromap.h" +// STL headers +#include <set> +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" + +static std::string clog; +static std::set<std::string> dlog; + +std::ostream& operator<<(std::ostream& out, const std::set<std::string>& strset) +{ +    out << '{'; +    const char* delim = ""; +    for (std::set<std::string>::const_iterator si(strset.begin()), se(strset.end()); +         si != se; ++si) +    { +        out << delim << '"' << *si << '"'; +        delim = ", "; +    } +    out << '}'; +    return out; +} + +struct Chalk +{ +    int dummy; +    std::string name; + +    Chalk(): +        dummy(0) +    { +        clog.append("a"); +    } + +    ~Chalk() +    { +        dlog.insert("a"); +    } + +private: +    Chalk(const Chalk&);            // no implementation +}; + +struct Cheese +{ +    std::string name; + +    Cheese() +    { +        clog.append("e"); +    } + +    ~Cheese() +    { +        dlog.insert("e"); +    } + +private: +    Cheese(const Cheese&);          // no implementation +}; + +struct Chowdah +{ +    char displace[17]; +    std::string name; + +    Chowdah() +    { +        displace[0] = '\0'; +        clog.append("o"); +    } + +    ~Chowdah() +    { +        dlog.insert("o"); +    } + +private: +    Chowdah(const Chowdah&);        // no implementation +}; + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct llheteromap_data +    { +        llheteromap_data() +        { +            clog.erase(); +            dlog.clear(); +        } +    }; +    typedef test_group<llheteromap_data> llheteromap_group; +    typedef llheteromap_group::object object; +    llheteromap_group llheteromapgrp("llheteromap"); + +    template<> template<> +    void object::test<1>() +    { +        set_test_name("create, get, delete"); + +        { +            LLHeteroMap map; + +            { +                // create each instance +                Chalk& chalk = map.obtain<Chalk>(); +                chalk.name = "Chalk"; + +                Cheese& cheese = map.obtain<Cheese>(); +                cheese.name = "Cheese"; + +                Chowdah& chowdah = map.obtain<Chowdah>(); +                chowdah.name = "Chowdah"; +            } // refs go out of scope + +            { +                // verify each instance +                Chalk& chalk = map.obtain<Chalk>(); +                ensure_equals(chalk.name, "Chalk"); + +                Cheese& cheese = map.obtain<Cheese>(); +                ensure_equals(cheese.name, "Cheese"); + +                Chowdah& chowdah = map.obtain<Chowdah>(); +                ensure_equals(chowdah.name, "Chowdah"); +            } +        } // destroy map + +        // Chalk, Cheese and Chowdah should have been created in specific order +        ensure_equals(clog, "aeo"); + +        // We don't care what order they're destroyed in, as long as each is +        // appropriately destroyed. +        std::set<std::string> dtorset; +        for (const char* cp = "aeo"; *cp; ++cp) +            dtorset.insert(std::string(1, *cp)); +        ensure_equals(dlog, dtorset); +    } +} // namespace tut | 
