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:
parent
f5e00e5b51
commit
a20f1cdf13
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user