Skip to content

Commit

Permalink
Implementing a pointer cast registry mechanism
Browse files Browse the repository at this point in the history
This mechanism allows users to autowire types that are completely undefined.  Generally speaking, any function whose behavior might depend on whether the Autowired instance has been autowired or not will require that the type be completely defined.  This includes obvious functions, such as `Autowired::operator->`, but also less obvious functions such as `Autowired::NotifyWhenAutowired` and `Autowired::operator bool`.

As a trapdoor, the function `Autowired::get_unsafe` has been added.  This function does not require that the autowired type be registered, but inconsistent behavior will result if it's not, especially when dealing with class interfaces.
  • Loading branch information
codemercenary committed Dec 30, 2014
1 parent 3ab6096 commit 8a39421
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 12 deletions.
42 changes: 37 additions & 5 deletions autowiring/AutowirableSlot.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,33 @@ class AutowirableSlot:
return typeid(T);
}

bool IsAutowired(void) const { return !!get(); }
bool IsAutowired(void) const {
// If the user wishes to know if this type is instantiated, we will require that a full definition
// of this type MUST be available. The reason for this is that, if the user wishes to know if a
// type is autowired, they are required at a minimum to know what that type's inheritance relations
// are to other types in the system.
(void) autowiring::fast_pointer_cast_initializer<Object, T>::sc_init;
return !!get();
}

/// <remarks>
/// Obtains a pointer to the underlying type, if autowired
/// </remarks>
T* get(void) const {
// For now, we require that the full type be available to use this method
(void) autowiring::fast_pointer_cast_initializer<Object, T>::sc_init;
return get_unsafe();
}

/// <remarks>
/// Obtains a pointer to the underlying type, if autowired
/// </remarks>
/// <remarks>
/// Users are STRONGLY discouraged from making use of this routine. This function does not cause
/// any runtime type information about T to wind up in any of Autowiring's type registries, and
/// this may prevent the type from ever being detected as autowirable as a result.
/// </remarks>
T* get_unsafe(void) const {
return
static_cast<const AnySharedPointerT<T>*>(
static_cast<const AnySharedPointer*>(
Expand All @@ -152,15 +176,23 @@ class AutowirableSlot:
)->slot()->get().get();
}

T* operator->(void) const {
return get();
}

explicit operator bool(void) const {
return IsAutowired();
}

T* operator->(void) const {
// Initialize any blind fast casts to the actually desired type. This is one of a few points
// where we can guarantee that the type will be completely defined, because the user is about
// to make use of this type.
(void) autowiring::fast_pointer_cast_initializer<Object, T>::sc_init;
return get();
}

T& operator*(void) const {
// We have to initialize here, in the operator context, because we don't actually know if the
// user will be making use of this type.
(void) autowiring::fast_pointer_cast_initializer<Object, T>::sc_init;

return *get();
}

Expand Down
17 changes: 15 additions & 2 deletions autowiring/Autowired.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,17 @@ class AutoRequired:
};

/// <summary>
/// Similar to Autowired, but doesn't defer creation if types doesn't already exist
/// Provides restricted, non-blocking, and typically faster services than Autowired
/// </summary>
/// <remarks>
/// AutowiredFast allows queries to be conducted against contexts that may be in teardown,
/// and also generally operates with fewer memory allocations and better performance than
/// Autowired. As a drawback, notifications on AutowiredFast cannot be attached, and the
/// field will not be updated in the case of post-hoc satisfaction--the value is effectively
/// a constant after initialization. AutowiredFast also requires that the autowired type
/// be completely defined before construction. By comparison, Autowired fields do not ever
/// need to be defined.
/// </remarks>
template<class T>
class AutowiredFast:
public std::shared_ptr<T>
Expand All @@ -261,11 +270,15 @@ class AutowiredFast:

// !!!!! Read comment in AutoRequired if you get a compiler error here !!!!!
AutowiredFast(const std::shared_ptr<CoreContext>& ctxt = CoreContext::CurrentContext()) {
(void) autowiring::fast_pointer_cast_initializer<Object, T>::sc_init;

if (ctxt)
ctxt->FindByTypeRecursive(*this);
}

AutowiredFast(const CoreContext* pCtxt) {
(void) autowiring::fast_pointer_cast_initializer<Object, T>::sc_init;

pCtxt->FindByTypeRecursive(*this);
}

Expand All @@ -277,7 +290,7 @@ class AutowiredFast:
return std::shared_ptr<T>::get();
}

bool IsAutowired(void) const {return std::shared_ptr<T>::get() != nullptr;}
bool IsAutowired(void) const { return std::shared_ptr<T>::get() != nullptr; }
};

/// <summary>
Expand Down
14 changes: 11 additions & 3 deletions autowiring/CoreContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -515,8 +515,10 @@ class CoreContext:
// Creator proxy, knows how to create the type we intend to inject
typedef autowiring::CreationRules<T, Args...> CreationRules;

// Add this type to the TypeRegistry
// Add this type to the TypeRegistry, also ensure that we initialize support for blind
// fast pointer cast to Object.
(void) RegType<T>::r;
(void) autowiring::fast_pointer_cast_initializer<Object, T>::sc_init;

// First see if the base object type has already been injected. This is also necessary to
// ensure that a memo slot is created for the type by itself, in cases where the injected
Expand Down Expand Up @@ -550,8 +552,9 @@ class CoreContext:
FindByType(retVal);
}

// Factory registration if sensible to do so, but only after the underlying type has been
// added, so that the proper type can succeed
// Factory registration if sensible to do so, but only after the underlying type has been added
// This ensures that any creation operations that happen as a consequence of factory registration
// can correctly back-reference the factory proper via autowiring
RegisterFactory(*retVal, autowiring::member_new_type<typename CreationRules::TActual>());
return std::static_pointer_cast<T>(retVal);
}
Expand Down Expand Up @@ -1044,6 +1047,11 @@ class CoreContext::AutoFactoryFn<std::tuple<Ts...>, Fn> :

template<typename T, typename... Args>
T* autowiring::crh<autowiring::construction_strategy::foreign_factory, T, Args...>::New(CoreContext& ctxt, Args&&... args) {
// We need to ensure that we can perform a find-by-type cast correctly, so
// the dynamic caster entry is added to the registry
autowiring::fast_pointer_cast_initializer<Object, CoreContext::AutoFactory<T>>::sc_init;

// Now we can go looking for this type:
AnySharedPointerT<CoreContext::AutoFactory<T>> af;
ctxt.FindByType(af);
if(!af)
Expand Down
4 changes: 2 additions & 2 deletions autowiring/SharedPointerSlot.h
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ struct SharedPointerSlotT<T, true>:

bool try_assign(const std::shared_ptr<Object>& rhs) override {
// Just perform a dynamic cast:
auto casted = autowiring::fast_pointer_cast<T>(rhs);
auto casted = autowiring::fast_pointer_cast_blind<T, Object>::cast(rhs);
if (!casted)
return false;

Expand All @@ -355,7 +355,7 @@ struct SharedPointerSlotT<T, true>:
}

virtual operator std::shared_ptr<Object>(void) const override {
return autowiring::fast_pointer_cast<Object>(SharedPointerSlotT<T, false>::get());
return autowiring::fast_pointer_cast_blind<Object, T>::cast(SharedPointerSlotT<T, false>::get());
}

using SharedPointerSlotT<T, false>::operator=;
Expand Down
69 changes: 69 additions & 0 deletions autowiring/fast_pointer_cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
#include TYPE_TRAITS_HEADER

namespace autowiring {
template<class T, class U>
struct fast_pointer_cast_blind;

template<class T, class U>
struct fast_pointer_cast_initializer;

/// <summary>
/// Identical to static_pointer_cast if U inherits T, dynamic_pointer_cast otherwise
/// </summary>
Expand All @@ -13,6 +19,8 @@ namespace autowiring {
std::is_base_of<T, U>::value && !std::is_same<T, U>::value,
typename std::shared_ptr<T>
>::type fast_pointer_cast(const std::shared_ptr<U>& Other) {
(void) fast_pointer_cast_initializer<T, U>::sc_init;
(void) fast_pointer_cast_initializer<U, T>::sc_init;
return std::static_pointer_cast<T, U>(Other);
};

Expand All @@ -23,6 +31,8 @@ namespace autowiring {
std::is_class<T>::value,
std::shared_ptr<T>
>::type fast_pointer_cast(const std::shared_ptr<U>& Other) {
(void) fast_pointer_cast_initializer<T, U>::sc_init;
(void) fast_pointer_cast_initializer<U, T>::sc_init;
return std::dynamic_pointer_cast<T, U>(Other);
}

Expand All @@ -34,6 +44,8 @@ namespace autowiring {
) && !std::is_same<T, U>::value,
std::shared_ptr<T>
>::type fast_pointer_cast(const std::shared_ptr<U>&) {
(void) fast_pointer_cast_initializer<T, U>::sc_init;
(void) fast_pointer_cast_initializer<U, T>::sc_init;
return std::shared_ptr<T>();
}

Expand All @@ -44,4 +56,61 @@ namespace autowiring {
>::type fast_pointer_cast(const std::shared_ptr<U>& ptr) {
return ptr;
}

/// <summary>
/// Holds a fast pointer cast function pointer
/// </summary>
/// <remarks>
/// This method holds the implementation of the blind fast pointer cast function
/// </summary>
template<class T, class U>
struct fast_pointer_cast_blind {
/// <summary>
/// A delayed instantiation fast pointer cast
/// </summary>
/// <remarks>
/// This method will perform a correct fast pointer cast to the destination type without _any_ knowledge
/// of either T or U being required. It's able to do this as long as there is another location somewhere
/// in the application where a fast_pointer_cast is being used, because the fast_pointer_cast type has a
/// small registry of cast functions.
/// </remarks>
static std::shared_ptr<T>(*cast)(const std::shared_ptr<U>&);
};

/// <summary>
/// Trivial case specialization
/// </summary>
template<class T>
struct fast_pointer_cast_blind<T, T> {
std::shared_ptr<T> cast(const std::shared_ptr<T>& rhs) {
return rhs;
}
};

/// <summary>
/// Null implementation of the fast pointer cast function
/// </summary>
template<class T, class U>
std::shared_ptr<T> null_cast(const std::shared_ptr<U>&) {
return nullptr;
}

// Default cast routine is going to be a do-nothing function
template<class T, class U>
std::shared_ptr<T>(*fast_pointer_cast_blind<T, U>::cast)(const std::shared_ptr<U>&) = &null_cast<T, U>;

/// <summary>
/// Initializer for the fast pointer cast holder
/// </summary>
template<class T, class U>
struct fast_pointer_cast_initializer {
fast_pointer_cast_initializer(void) {
// Update the cast routine:
fast_pointer_cast_blind<T, U>::cast = &fast_pointer_cast<T, U>;
}
static const fast_pointer_cast_initializer sc_init;
};

template<class T, class U>
const fast_pointer_cast_initializer<T, U> fast_pointer_cast_initializer<T, U>::sc_init;
}
7 changes: 7 additions & 0 deletions src/autowiring/test/AutowiringTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,10 @@ TEST_F(AutowiringTest, TestFailureOfDynamicCast) {
ASSERT_EQ(dynamic_cast<PrivateBase*>(pub), nullptr) << "Dynamic cast failed to give nullptr when cross casting to a private base class";
static_assert(!std::is_base_of<Derived, PrivateBase>::value, "is_base_of said a private base was a base");
}

class CompletelyUndefinedType;

TEST_F(AutowiringTest, CanAutowireCompletelyUndefinedType) {
Autowired<CompletelyUndefinedType> cut;
ASSERT_EQ(nullptr, cut.get_unsafe()) << "Autowiring of a completely undefined type succeeded unexpectedly";
}

0 comments on commit 8a39421

Please sign in to comment.