1
0
forked from Alepha/Alepha

Consolidate the StaticValue implementations.

This commit is contained in:
2023-11-12 05:23:20 -05:00
parent 5db7ad01f8
commit 4682fe3ffb
6 changed files with 154 additions and 197 deletions

View File

@ -15,11 +15,12 @@ static_assert( __cplusplus > 2020'99 );
#include <Alepha/Utility/evaluation_helpers.h> #include <Alepha/Utility/evaluation_helpers.h>
#include <Alepha/Utility/StaticValue.h>
#include <Alepha/IOStreams/OutUnixFileBuf.h> #include <Alepha/IOStreams/OutUnixFileBuf.h>
#include "Enum.h" #include "Enum.h"
#include "ProgramOptions.h" #include "ProgramOptions.h"
#include "StaticValue.h"
#include "string_algorithms.h" #include "string_algorithms.h"
#include "AutoRAII.h" #include "AutoRAII.h"
@ -46,6 +47,7 @@ namespace Alepha::Hydrogen ::detail:: Console_m
using namespace std::literals::string_literals; using namespace std::literals::string_literals;
using namespace Utility::exports::evaluation_helpers; using namespace Utility::exports::evaluation_helpers;
using Utility::StaticValue;
namespace C namespace C
{ {

View File

@ -8,8 +8,6 @@ static_assert( __cplusplus > 2020'99 );
#include <ostream> #include <ostream>
#include <optional> #include <optional>
#include <Alepha/StaticValue.h>
#include <Alepha/IOStreams/StreamState.h> #include <Alepha/IOStreams/StreamState.h>
namespace Alepha::Hydrogen::IOStreams ::detail:: delimiters_m namespace Alepha::Hydrogen::IOStreams ::detail:: delimiters_m

View File

@ -7,9 +7,10 @@ static_assert( __cplusplus > 2020'99 );
#include <Alepha/Console.h> #include <Alepha/Console.h>
#include <Alepha/word_wrap.h> #include <Alepha/word_wrap.h>
#include <Alepha/StaticValue.h>
#include <Alepha/error.h> #include <Alepha/error.h>
#include <Alepha/Utility/StaticValue.h>
#include <Alepha/System/programName.h> #include <Alepha/System/programName.h>
namespace Alepha::Hydrogen ::detail:: ProgramOptions_m namespace Alepha::Hydrogen ::detail:: ProgramOptions_m
@ -24,6 +25,7 @@ namespace Alepha::Hydrogen ::detail:: ProgramOptions_m
} }
using namespace std::literals::string_literals; using namespace std::literals::string_literals;
using Utility::StaticValue;
struct OptionMissingArgumentError struct OptionMissingArgumentError
: virtual std::runtime_error : virtual std::runtime_error

View File

@ -1,157 +0,0 @@
static_assert( __cplusplus > 2020'99 );
#pragma once
#include <Alepha/Alepha.h>
#include <tuple>
#include <boost/noncopyable.hpp>
namespace Alepha::Hydrogen ::detail:: StaticValue_m
{
inline namespace exports
{
template< typename T, typename init_helper >
class StaticValue;
}
template< typename T >
struct default_init
{
constexpr decltype( auto )
operator() () const noexcept
{
return new T{};
}
};
/*!
* Static value with low access overhead.
*
* In C++, function-static values in functions can have significant overhead to access, because they use an
* atomic check to guarantee threadsafe initialization. Alternatively, namespace-scope static values have their
* own initialization problems, because of the initialization order rules in C++. `Alepha::StaticValue` is a
* mechanism to provide a halfway-between solution. `Alepha::StaticValue` objects should be defined at
* namespace-scope. Unlike normal namespace-scope variables, they are initialized at first access. Unlike
* function-static values, the initialization is not guarded by an atomic access.
*
* `StaticValue` objects solve the initialization order issues of normal namespace-statics because it will
* initialize on first use. They solve the performance issues of function-statics, because of the
* non-atomic initialization technique. This means that the most appropriate use pattern is to guarantee
* initialization during a single-threaded portion of program execution. The idiom is meant to be
* a drop-in replacement (in usage syntax) for a function which returns a static value. However, it now has
* its own set of limitations -- it is not possible to initialize in a threadsafe manner.
*
* Example usage:
*
* ```
* namespace MyComponent
* {
* StaticValue< std::string > globalMessage;
*
* auto initGlobalMessage= globalMessage.init();
*
* void
* printGlobalMessage()
* {
* std::cout << globalMessage() << std::endl;
* }
* }
* ```
*
* If `printGlobalMessage` is called from any static initializer, before `initGlobalMessage` is initialized,
* then that first use will initialize the value. If `initGlobalMessage` is initialized before any usage
* of `globalMessage`, then the value will be initialized by that initializer.
*
* For any program which does not start any threads before `main()` starts, this pattern (declare a
* `StaticValue` and declare an initializer step) will always be threadsafe and proper. For a program
* which can guarantee that a `StaticValue` is used in static initializers before `main()` or threads start,
* it is possible to decline making a static initializer call to `StaticValue::init`.
*
* Exploring this further, here's a typical function-static used with initializers:
* ```
* namespace MyComponent
* {
* auto &
* registry()
* {
* static std::vector< std::string > registrations;
* return registrations;
* }
*
* auto registration= Alepha::enroll<=[]
* {
* registry().push_back( "Hello World" );
* };
* }
* ```
* The above could be rewritten to have the effects of `StaticValue` thus:
* ```
* namespace MyComponent
* {
* namespace storage
* {
* std::vector< std::string > *registrations= nullptr;
* }
*
* auto &
* registry()
* {
* using namespace storage;
* if( registrations == nullptr ) registrations= new decltype( *registrations );
* return *registrations;
* }
*
* auto registration= Alepha::enroll<=[]
* {
* registry().push_back( "Hello World" );
* };
* }
* ```
* However, that is complicated and difficult to get right. Thus `StaticValue` helps
* us by radically simplifying the declaration syntax:
* ```
* namespace MyComponent
* {
* StaticValue< std::vector< std::string > > registrations;
*
* auto registration= Alepha::enroll<=[]
* {
* registrations().push_back( "Hello World" );
* };
* }
* ```
*/
template< typename T, typename init_helper= default_init< T > >
class exports::StaticValue : boost::noncopyable
{
private:
T *storage= nullptr;
public:
~StaticValue()
{
delete storage;
}
constexpr decltype( auto )
get() noexcept
{
if( storage == nullptr ) [[unlikely]] storage= init_helper{}();
assert( storage != nullptr );
return *storage;
}
constexpr decltype( auto )
operator() () noexcept
{
return get();
}
};
}
namespace Alepha::Hydrogen::inline exports::inline StaticValue_m
{
using namespace detail::StaticValue_m::exports;
}

View File

@ -40,7 +40,7 @@ namespace Alepha::Hydrogen::Testing
using namespace std::literals::string_literals; using namespace std::literals::string_literals;
using namespace Utility::exports::evaluation_helpers; using namespace Utility::exports::evaluation_helpers;
using namespace Utility::exports::static_value; using namespace Utility::exports::StaticValue_m;
struct TestName struct TestName
{ {

View File

@ -2,46 +2,158 @@ static_assert( __cplusplus > 2020'99 );
#pragma once #pragma once
namespace Alepha::Hydrogen::Utility #include <Alepha/Alepha.h>
#include <cassert>
#include <tuple>
#include <boost/noncopyable.hpp>
namespace Alepha::Hydrogen::Utility ::detail:: StaticValue_m
{ {
inline namespace exports { inline namespace static_value {} } inline namespace exports
namespace detail::static_value
{ {
inline namespace exports {} template< typename T, typename init_helper >
class StaticValue;
}
template< typename T > template< typename T >
struct default_init struct default_init
{
constexpr decltype( auto )
operator() () const noexcept
{ {
T *operator()() const { return new T{}; } return new T{};
};
namespace exports
{
template< typename T, typename Init= default_init< T > >
struct StaticValue;
} }
};
template< typename T, typename Init > /*!
struct exports::StaticValue * Static value with low access overhead.
{ *
private: * In C++, function-static values in functions can have significant overhead to access, because they use an
T *storage= nullptr; * atomic check to guarantee threadsafe initialization. Alternatively, namespace-scope static values have their
* own initialization problems, because of the initialization order rules in C++. `Alepha::StaticValue` is a
public: * mechanism to provide a halfway-between solution. `Alepha::StaticValue` objects should be defined at
decltype( auto ) * namespace-scope. Unlike normal namespace-scope variables, they are initialized at first access. Unlike
get() * function-static values, the initialization is not guarded by an atomic access.
{ *
if( not storage ) storage= Init{}(); * `StaticValue` objects solve the initialization order issues of normal namespace-statics because it will
return *storage; * initialize on first use. They solve the performance issues of function-statics, because of the
} * non-atomic initialization technique. This means that the most appropriate use pattern is to guarantee
* initialization during a single-threaded portion of program execution. The idiom is meant to be
decltype( auto ) operator ()() { return get(); } * a drop-in replacement (in usage syntax) for a function which returns a static value. However, it now has
}; * its own set of limitations -- it is not possible to initialize in a threadsafe manner.
} *
* Example usage:
namespace exports::static_value *
* ```
* namespace MyComponent
* {
* StaticValue< std::string > globalMessage;
*
* auto initGlobalMessage= globalMessage.init();
*
* void
* printGlobalMessage()
* {
* std::cout << globalMessage() << std::endl;
* }
* }
* ```
*
* If `printGlobalMessage` is called from any static initializer, before `initGlobalMessage` is initialized,
* then that first use will initialize the value. If `initGlobalMessage` is initialized before any usage
* of `globalMessage`, then the value will be initialized by that initializer.
*
* For any program which does not start any threads before `main()` starts, this pattern (declare a
* `StaticValue` and declare an initializer step) will always be threadsafe and proper. For a program
* which can guarantee that a `StaticValue` is used in static initializers before `main()` or threads start,
* it is possible to decline making a static initializer call to `StaticValue::init`.
*
* Exploring this further, here's a typical function-static used with initializers:
* ```
* namespace MyComponent
* {
* auto &
* registry()
* {
* static std::vector< std::string > registrations;
* return registrations;
* }
*
* auto registration= Alepha::enroll<=[]
* {
* registry().push_back( "Hello World" );
* };
* }
* ```
* The above could be rewritten to have the effects of `StaticValue` thus:
* ```
* namespace MyComponent
* {
* namespace storage
* {
* std::vector< std::string > *registrations= nullptr;
* }
*
* auto &
* registry()
* {
* using namespace storage;
* if( registrations == nullptr ) registrations= new decltype( *registrations );
* return *registrations;
* }
*
* auto registration= Alepha::enroll<=[]
* {
* registry().push_back( "Hello World" );
* };
* }
* ```
* However, that is complicated and difficult to get right. Thus `StaticValue` helps
* us by radically simplifying the declaration syntax:
* ```
* namespace MyComponent
* {
* StaticValue< std::vector< std::string > > registrations;
*
* auto registration= Alepha::enroll<=[]
* {
* registrations().push_back( "Hello World" );
* };
* }
* ```
*/
template< typename T, typename init_helper= default_init< T > >
class exports::StaticValue : boost::noncopyable
{ {
using namespace detail::static_value::exports; private:
} T *storage= nullptr;
public:
~StaticValue()
{
delete storage;
}
constexpr decltype( auto )
get() noexcept
{
if( storage == nullptr ) [[unlikely]] storage= init_helper{}();
assert( storage != nullptr );
return *storage;
}
constexpr decltype( auto )
operator() () noexcept
{
return get();
}
};
}
namespace Alepha::Hydrogen::Utility::inline exports::inline StaticValue_m
{
using namespace detail::StaticValue_m::exports;
} }