1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-15 15:49:36 +02:00

Doxygen comments for AttachedVirtualFunction

This commit is contained in:
Paul Licameli 2020-09-08 22:58:29 -04:00
parent f5e00e5b51
commit a20f1cdf13

View File

@ -1,8 +1,9 @@
/********************************************************************** /*!********************************************************************
Audacity: A Digital Audio Editor Audacity: A Digital Audio Editor
AttachedVirtualFunction.h @file AttachedVirtualFunction.h
@brief Utility for non-intrusive definition of a new method on a base class
Paul Licameli Paul Licameli
@ -11,14 +12,29 @@ Paul Licameli
#ifndef __AUDACITY_ATTACHED_VIRTUAL_FUNCTION__ #ifndef __AUDACITY_ATTACHED_VIRTUAL_FUNCTION__
#define __AUDACITY_ATTACHED_VIRTUAL_FUNCTION__ #define __AUDACITY_ATTACHED_VIRTUAL_FUNCTION__
/* \brief Define a "virtual" function with multiple bodies chosen by type-switch
on the runtime class of the first argument, leaving the set of functions #include <functional>
#include <mutex>
#include <type_traits>
#include <utility>
#include "InconsistencyException.h"
//! Class template generates single-dispatch, open method registry tables
/*!
Defines a "virtual" function with multiple bodies chosen by type-switch
on the runtime class of the first argument, leaving the set of overrides
open-ended for extension, but also requiring no modification of the definition open-ended for extension, but also requiring no modification of the definition
of the base class of the type hierarchy that is switched on. of the base class of the type hierarchy that is switched on.
The invocation of the function is not as efficient as for true virtual functions The invocation of the function is not as efficient as for true virtual functions
but the advantage of this utility is greater compilation decoupling. A client but the advantage of this utility is greater compilation decoupling. Client code
can attach its own virtual functions to a class hierarchy in the core. can attach its own virtual functions, non-intrusively, to the root of a class hierarchy
defined in the core.
There is no collection of overriding function pointers into virtual function tables by the
linker, but a registration of overrides into a table at static initialization time. This allows
those implementations to be defined wherever is convenient, even in dynamically loaded
libraries.
Beware that invocation of the function should not be done during initialization Beware that invocation of the function should not be done during initialization
of file scope static objects. Dispatch might not go to the correct subclass of file scope static objects. Dispatch might not go to the correct subclass
@ -26,32 +42,25 @@ case if initializations are not yet complete.
Example usage: Example usage:
//////// A core class:
// Core classes in Host.h: ```
// file Host.h
class AbstractHost class AbstractHost
{ {
// ... // ...
}; };
```
class SpecialHost : public Abstract Host Declare the attached function
{ (in a header that Host.cpp need not include at all)
// ... as a specialization of the template:
}; ```
// file Client.h
class ExtraSpecialHost : public SpecialHost
{
// ...
};
////////
// Declare the root of the attached function hierarchy in Client.h,
// which Host.cpp need not include at all:
enum class ErrorCode { Ok, Bad, // ... enum class ErrorCode { Ok, Bad, // ...
}; // a return type for our function }; // a return type for our function
// First this empty structure serving just to distinguish among instantiations // First this empty structure serving just to distinguish among instantiations
// of AttachedVirtualFunction with otherwise identical arguments // of AttachedVirtualFunction with otherwise identical parameters
// An incomplete type is enough // An incomplete type is enough
struct DoSomethingTag; struct DoSomethingTag;
@ -63,9 +72,27 @@ AttachedVirtualFunction<
AbstractHost, // class to be switched on by runtime type AbstractHost, // class to be switched on by runtime type
int, double // other arguments int, double // other arguments
>; >;
```
//////// Definitions needed:
// Usage of the "virtual function" ```
//file Client.cpp
// Define the default function body here (as a function returning a function!)
template<> auto DoSomething::Implementation() -> Function {
return [](AbstractHost &host, int arg1, double arg2) {
return ErrorCode::Ok;
};
// or you could return nullptr instead of a lambda to force InconsistencyException
// at runtime if the virtual function is invoked for a host subclass for which no override
// was defined.
}
// Must also guarantee construction of an instance of class DoSomething at least
// once before any use of DoSomething::Call()
static DoSomething registerMe;
```
Usage of the method somewhere else:
```
#include "Client.h" #include "Client.h"
void UseDoSomething( AbstractHost &host ) void UseDoSomething( AbstractHost &host )
{ {
@ -73,39 +100,39 @@ void UseDoSomething( AbstractHost &host )
auto error = DoSomething::Call( host, 0, 1.0 ); auto error = DoSomething::Call( host, 0, 1.0 );
// ... // ...
} }
```
//////// Derived classes from AbstractHost, not needing Client.h:
// Out-of-line registrations needed in Client.cpp: ```
// file SpecialHost.h
#include "Host.h"
class SpecialHost : public AbstractHost
{
// ...
};
// Define the default function body here (as a function returning a function!) class ExtraSpecialHost : public SpecialHost
template<> auto DoSomething::Implementation() -> Function { {
return [](AbstractHost &host, int arg1, double arg2) { // ...
return ErrorCode::Ok; };
}; ```
// or you could return nullptr to force InconsistencyException at runtime if
// the virtual function is invoked for a host subclass for which no override
// was defined
}
// Must also guarantee construction of an instance of class DoSomething at least
// once before any use of DoSomething::Call()
static DoSomething registerMe;
//////// Overrides of the method, defined in any other .cpp file:
// An override of the function is defined in SpecialClient.cpp, ```
// building up a hierarchy of function bodies parallel to the host class #include "SpecialHost.h"
// hierarchy #include "Client.h"
// An override of the function, building up a hierarchy of function bodies parallel
// to the host class hierarchy
using DoSomethingSpecial = DoSomething::Override< SpecialHost >; using DoSomethingSpecial = DoSomething::Override< SpecialHost >;
template<> template<> auto DoSomethingSpecial::Implementation() -> Function { template<> template<> auto DoSomethingSpecial::Implementation() -> Function {
// The function can be defined assuming downcast of the first argument // The function can be defined without casting the first argument
// has been done
return [](SpecialHost &host, int arg1, double arg2) { return [](SpecialHost &host, int arg1, double arg2) {
return arg1 == 0 ? ErrorCode::Ok : ErrorCode::Bad; return arg1 == 0 ? ErrorCode::Ok : ErrorCode::Bad;
}; };
} }
static DoSomethingSpecial registerMe; static DoSomethingSpecial registerMe;
//////// // A further override, demonstrating call-through too
// A further override in ExtraSpecialClient.cpp, demonstrating call-through too
using DoSomethingExtraSpecial = using DoSomethingExtraSpecial =
DoSomething::Override< ExtraSpecialHost, DoSomethingSpecial >; DoSomething::Override< ExtraSpecialHost, DoSomethingSpecial >;
template<> template<> template<> template<>
@ -120,48 +147,50 @@ auto DoSomethingExtraSpecial::Implementation() -> Function {
return result; return result;
}; };
} }
static DoSomethinExtraSpecial registerMe; static DoSomethingExtraSpecial registerMe;
```
@tparam Tag an incomplete type, to distinguish methods with otherwise identical parameters
@tparam Return the value returned by each override
@tparam This type of the first argument, a class with at least one true virtual function, the root of the hierarchy for the run-time type-switch
@tparam Arguments any number of types for the second and later arguments
*/ */
#include <functional>
#include <mutex>
#include <type_traits>
#include <utility>
#include "InconsistencyException.h"
template< typename Tag, typename Return, typename This, typename... Arguments > template< typename Tag, typename Return, typename This, typename... Arguments >
class AttachedVirtualFunction class AttachedVirtualFunction
{ {
public: public:
// These member names are declared in this class template and redeclared //! This member name is declared in this class template and redeclared in each override
// in each override
using Object = This; using Object = This;
//! This member name is declared in this class template and redeclared in each override
using Function = std::function< Return( Object&, Arguments... ) >; using Function = std::function< Return( Object&, Arguments... ) >;
// A function returning a std::function, which you define elsewhere; //! A function returning a std::function, which you must define so that the program links
// it may return nullptr in case this must act somewhat as a "pure virtual", /*! It may return nullptr in case this must act somewhat as a "pure virtual",
// throwing InconsistencyException if the function is invoked on a subclass throwing InconsistencyException if the function is invoked on a subclass
// for which no override was defined for which no override was defined */
static Function Implementation(); static Function Implementation();
// This class must be instantiated once at least to register the function in //! At least one static instance must be created; more instances are harmless
// a table, but may be instantiated multiply (and will be if there are any /*! (There will be others if there are any overrides.) */
// overrides)
AttachedVirtualFunction() AttachedVirtualFunction()
{ {
static std::once_flag flag; static std::once_flag flag;
std::call_once( flag, []{ Register<This>( Implementation() ); } ); std::call_once( flag, []{ Register<This>( Implementation() ); } );
} }
// For defining overrides of the virtual function; template arguments //! For defining overrides of the method
// are the more specific subclass and the immediately overridden version /*!
// of the function, defaulting to the base version @tparam Subclass the more specific subclass of @b This
@tparam Overridden The immediately overridden version, defaulting to the base version
*/
template< template<
typename Subclass, typename Overridden = AttachedVirtualFunction > typename Subclass, typename Overridden = AttachedVirtualFunction >
struct Override : Overridden struct Override : Overridden
{ {
//! Shadowing Overridden::Object
using Object = Subclass; using Object = Subclass;
//! Shadowing Overridden::Function, giving the first argument a more specific type
using Function = std::function< Return( Object&, Arguments... ) >; using Function = std::function< Return( Object&, Arguments... ) >;
// Check that inheritance is correct // Check that inheritance is correct
@ -170,16 +199,17 @@ public:
"overridden class must be a base of the overriding class" "overridden class must be a base of the overriding class"
); );
// A function returning a std::function that must be defined out-of-line //! A function returning a std::function that must be defined so that the program links
static Function Implementation(); static Function Implementation();
// May be used in the body of the overriding function, defining it in //! May be used in the body of the overriding function, defining it in terms of the overridden one
// terms of the overridden one:
static Return Callthrough( static Return Callthrough(
typename Overridden::Object &object, Arguments &&...arguments ) typename Overridden::Object &object, Arguments &&...arguments )
{ {
return Overridden::Implementation()( return Overridden::Implementation()(
object, std::forward< Arguments >( arguments )... ); object, std::forward< Arguments >( arguments )... );
} }
//! At least one static instance must be created; more instances are harmless
/*! (There will be others if there are any further overrides.) */
Override() Override()
{ {
static std::once_flag flag; static std::once_flag flag;
@ -195,7 +225,11 @@ public:
} }
}; };
static Return Call( This &obj, Arguments &&...arguments ) //! Invoke the method -- but only after static initialization time
static Return Call(
This &obj, //!< Object on which to type-switch at run-time
Arguments &&...arguments //!< other arguments
)
{ {
try { try {
// Note that the constructors of this class and overrides cause // Note that the constructors of this class and overrides cause
@ -211,9 +245,11 @@ public:
for ( ; iter != end; ++iter ) { for ( ; iter != end; ++iter ) {
auto &entry = *iter; auto &entry = *iter;
if ( entry.predicate( &obj ) ) if ( entry.predicate( &obj ) )
// This might throw std::bad_function_call on a null function
return entry.function( return entry.function(
obj, std::forward< Arguments >( arguments )... ); obj, std::forward< Arguments >( arguments )... );
} }
// If not found, also throw
throw std::bad_function_call{}; throw std::bad_function_call{};
} }
catch ( const std::bad_function_call& ) { catch ( const std::bad_function_call& ) {
@ -236,6 +272,7 @@ private:
using Predicate = std::function< bool( This* ) >; using Predicate = std::function< bool( This* ) >;
//! Member of registry of implementations of the method
struct Entry struct Entry
{ {
Predicate predicate; Predicate predicate;