-
Notifications
You must be signed in to change notification settings - Fork 92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use Meyers singletons #1222
base: devel
Are you sure you want to change the base?
Use Meyers singletons #1222
Conversation
initialized on their first call and return a reference to it; avoids static initialization order fiasco) instead of pointers allocated on demand. Use std::shared_ptr for LoadedLibraries loaded libraries so that libraries and aliases can share the same loader pointers without memory leaks. Use C++17 "inline" with static const members so that they can be initialized in-class and won't have multiple definition errors if defined in a header.
CLANG-FORMAT TEST - PASSED |
CMAKE-FORMAT TEST - PASSED |
@@ -230,7 +230,7 @@ ElemLoader::loadLibrary(const std::string& elemlib, std::ostream& err_os) | |||
// loop all the elements in the element lib | |||
for ( auto& elempair : libpair.second ) { | |||
// loop all the loaders in the element | |||
for ( auto* loader : elempair.second ) { | |||
for ( auto& loader : elempair.second ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allows loader
to be either a regular pointer or a smart pointer. The previous code only allowed regular pointers.
return iter->second; | ||
} | ||
} | ||
BaseBuilder* getBuilder(const std::string& name) { return factories_[name]; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since I do not see anywhere factories_
is used in such a way that adding nullptr
entries would be a problem, this function can simply reduced to this subscript operator, which will create and return a nullptr
entry if the name doesn't exist.
static Map libraries; // Database | ||
auto& lib = libraries[name]; | ||
if ( !lib ) lib = new Library(name); | ||
return lib; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the Meyers singleton initializing the map on the first call to getLibrary()
.
It also uses a reference to the map entry and checks if it's nullptr
, and if it is (such as if it did not exist before), sets it to allocated memory.
@@ -147,12 +127,9 @@ struct InstantiateBuilder | |||
{ | |||
static bool isLoaded() { return loaded; } | |||
|
|||
static const bool loaded; | |||
static inline const bool loaded = Base::Ctor::template add<T>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inline
can be used with static
class data members in C++17, and they can be initialized in-class. This prevents multiple definitions which could occur in the old code if the variable was ODR-used (had its address taken), since it was defined in the header file.
static constexpr
member variables are inline
by default, but the initializer here is not constexpr
(it possibly could be made so, if the operator()
and its return value were constexpr
).
if ( !cached_ ) { cached_ = new T(std::forward<Args>(ctorArgs)...); } | ||
return cached_; | ||
static T cached(std::forward<Args>(ctorArgs)...); | ||
return &cached; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another Meyers Singleton, in lieu of calling new
if a pointer is nullptr
(I did not see this CachedAllocator
used anywhere, and I doubt any code calls delete
on its returned address, so returning the address of a static
is okay).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it needs to be a pointer which can be delete
d, it simply needs to be changed to:
static T* cached = new T(std::forward<Args>(ctorArgs)...);
return cached;
{ | ||
static std::map<std::string, std::map<std::string, T*>> infos_; | ||
return infos_; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another Meyers singleton.
{ | ||
static Map libs; | ||
return libs; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again.
if ( !alias.empty() ) (*loaders_)[lib][alias].push_back(loader); | ||
return true; | ||
} | ||
std::shared_ptr<LibraryLoader> shared_loader(loader); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We create a std::shared_ptr
which owns the loader pointer passed in.
} | ||
auto& library = getLoaders()[lib]; | ||
if ( !alias.empty() && alias != name ) library[alias].push_back(shared_loader); | ||
library[name].push_back(std::move(shared_loader)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the last use of shared_loader
we use std::move()
so that it doesn't have the overhead of incrementing the reference count during the push_back()
and then decrementing the reference count when shared_loader
is destroyed. We move shared_loader
to the new entry without changing reference counts, and its destruction is a no-op.
}; | ||
|
||
class LoadedLibraries | ||
{ | ||
public: | ||
using InfoMap = std::map<std::string, std::list<LibraryLoader*>>; | ||
using InfoMap = std::map<std::string, std::deque<std::shared_ptr<LibraryLoader>>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::deque
is faster than std::list
and std::vector
for adding an unknown number of items repeatedly to the end of a list which grows. std::list
requires more dynamic memory allocations and is cache-unfriendly, and std::vector
requires moving the entire vector's data to a new location if it has to increase its capacity. std::deque
is also randomly accessible like std::vector
, but it's not contiguous. I would look at all places std::vector
and std::list
are used and see if std::deque
might be better.
{ | ||
static LibraryMap loaders; | ||
return loaders; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meyers singleton.
Status Flag 'Pre-Test Inspection' - - This Pull Request Requires Inspection... The code must be inspected by a member of the Team before Testing/Merging |
Use Meyers Singletons (
static
variables local to functions which are initialized on their first call and return a reference to it; avoidsstatic
initialization order fiasco) instead of pointers allocated on demand.Use
std::shared_ptr
forLoadedLibraries
loaded libraries so that libraries and aliases can share the same loader pointers without memory leaks. I was prompted to do this byvalgrind
:I can see other places where
std::shared_ptr
andstd::unique_ptr
should be used, but since the memory system is being overhauled, I won't touch the other places.inline
withstatic const
class members so that they can be defined in-class and won't have multiple definition errors if defined in a header.