SingletonPtr: API extensions, make constexpr

* Adjust definition to make the default constructor `constexpr`.
  This permits use in classes that want lazy initialization and their
  own `constexpr` constructor, such as `mstd::mutex`.

* Add `get_no_init()` method to allow an explicit optimisation for
  paths that know they won be the first call (such as
  `mstd::mutex::unlock`).

* Add `destroy()` method to permit destruction of the contained object.
  (`SingletonPtr`'s destructor does not call its destructor - a cheat
  to omit destructors of static objects). Needed if using in a class
  that needs proper destruction.
pull/11039/head
Kevin Bracey 2019-07-11 14:11:27 +03:00
parent 0aab1a9ea1
commit 0bb4c050b7
4 changed files with 98 additions and 19 deletions

View File

@ -345,15 +345,21 @@ protected:
enum SPIName { GlobalSPI };
#endif
// All members of spi_peripheral_s must be initialized to make the structure
// constant-initialized, and hence able to be omitted by the linker,
// as SingletonPtr now relies on C++ constant-initialization. (Previously it
// worked through C++ zero-initialization). And all the constants should be zero
// to ensure it stays in the actual zero-init part of the image if used, avoiding
// an initialized-data cost.
struct spi_peripheral_s {
/* Internal SPI name identifying the resources. */
SPIName name;
SPIName name = SPIName(0);
/* Internal SPI object handling the resources' state. */
spi_t spi;
spi_t spi{};
/* Used by lock and unlock for thread safety */
SingletonPtr<PlatformMutex> mutex;
/* Current user of the SPI */
SPI *owner;
SPI *owner = nullptr;
#if DEVICE_SPI_ASYNCH && TRANSACTION_QUEUE_SIZE_SPI
/* Queue of pending transfers */
SingletonPtr<CircularBuffer<Transaction<SPI>, TRANSACTION_QUEUE_SIZE_SPI> > transaction_buffer;

View File

@ -42,11 +42,17 @@ static const fhss_api_t *fhss_active_handle = NULL;
static EventQueue *equeue;
#endif
// All members of fhss_timeout_s must be initialized to make the structure
// constant-initialized, and hence able to be omitted by the linker,
// as SingletonPtr now relies on C++ constant-initialization. (Previously it
// worked through C++ zero-initialization). And all the constants should be zero
// to ensure it stays in the actual zero-init part of the image if used, avoiding
// an initialized-data cost.
struct fhss_timeout_s {
void (*fhss_timer_callback)(const fhss_api_t *fhss_api, uint16_t);
uint32_t start_time;
uint32_t stop_time;
bool active;
void (*fhss_timer_callback)(const fhss_api_t *fhss_api, uint16_t) = nullptr;
uint32_t start_time = 0;
uint32_t stop_time = 0;
bool active = false;
SingletonPtr<Timeout> timeout;
};

View File

@ -71,21 +71,51 @@ inline static void singleton_unlock(void)
#endif
}
/** Utility class for creating an using a singleton
/** Utility class for creating and using a singleton
*
* @note Synchronization level: Thread safe
*
* @note: This class must only be used in a static context -
* this class must never be allocated or created on the
* stack.
*
* @note: This class is lazily initialized on first use.
* This class is a POD type so if it is not used it will
* be garbage collected.
* This class has a constexpr default constructor so if it is
* not used as a non-local variable it will be garbage collected.
*
* @note: This class would normally be used in a static standalone
* context. It does not call the destructor of the wrapped object
* when it is destroyed, effectively ensuring linker exclusion of the
* destructor for static objects. If used in another context, such as
* a member of a normal class wanting "initialize on first-use"
* semantics on a member, care should be taken to call the destroy
* method manually if necessary.
*
* @note: If used as a sub-object of a class, that class's own
* constructor must be constexpr to achieve its exclusion by
* the linker when unused. That will require explicit
* initialization of its other members.
*
* @note: More detail on initialization: Formerly, SingletonPtr
* had no constructor, so was "zero-initialized" when non-local.
* So if enclosed in another class with no constructor, the whole
* thing would be zero-initialized, and linker-excludable.
* Having no constructor meant SingletonPtr was not constexpr,
* which limited applicability in other contexts. With its new
* constexpr constructor, it is now "constant-initialized" when
* non-local. This achieves the same effect as a standalone
* non-local object, but as a sub-object linker exclusion is
* now only achieved if the outer object is itself using a
* constexpr constructor to get constant-initialization.
* Otherwise, the outer object will be neither zero-initialized
* nor constant-initialized, so will be "dynamic-initialized",
* and likely to be left in by the linker.
*/
template <class T>
struct SingletonPtr {
// Initializers are required to make default constructor constexpr
// This adds no overhead as a static object - the compiler and linker can
// figure out that we are effectively zero-init, and either place us in
// ".bss", or exclude us if unused.
constexpr SingletonPtr() noexcept : _ptr(), _data() { }
/** Get a pointer to the underlying singleton
*
* @returns
@ -93,13 +123,13 @@ struct SingletonPtr {
*/
T *get() const
{
T *p = static_cast<T *>(core_util_atomic_load_ptr(&_ptr));
T *p = core_util_atomic_load(&_ptr);
if (p == NULL) {
singleton_lock();
p = static_cast<T *>(_ptr);
p = _ptr;
if (p == NULL) {
p = new (_data) T();
core_util_atomic_store_ptr(&_ptr, p);
core_util_atomic_store(&_ptr, p);
}
singleton_unlock();
}
@ -129,8 +159,42 @@ struct SingletonPtr {
return *get();
}
// This is zero initialized when in global scope
mutable void *_ptr;
/** Get a pointer to the underlying singleton
*
* Gets a pointer without initialization - can be
* used as an optimization when it is known that
* initialization must have already occurred.
*
* @returns
* A pointer to the singleton, or NULL if not
* initialized.
*/
T *get_no_init() const
{
return _ptr;
}
/** Destroy the underlying singleton
*
* The underlying singleton is never automatically destroyed;
* this is a potential optimization to avoid destructors
* being pulled into an embedded image on the exit path,
* which should never occur. The destructor can be
* manually invoked via this call.
*
* Unlike construction, this is not thread-safe. After this call,
* no further operations on the object are permitted.
*
* Is a no-op if the object has not been constructed.
*/
void destroy()
{
if (_ptr) {
_ptr->~T();
}
}
mutable T *_ptr;
#if __cplusplus >= 201103L && !defined __CC_ARM
// Align data appropriately (ARM Compiler 5 does not support alignas in C++11 mode)
alignas(T) mutable char _data[sizeof(T)];

View File

@ -46,7 +46,9 @@ retval
dequeue
assertation
destructor
destructors
constructor
constructors
ctor
dtor
dereference
@ -71,6 +73,7 @@ deasserted
getter
setter
preallocated
excludable
ascii
IPv
param