forked from Alepha/Alepha
Consolidate the StaticValue
implementations.
This commit is contained in:
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
157
StaticValue.h
157
StaticValue.h
@ -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;
|
|
||||||
}
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user