Skip to content

Commit df46bba

Browse files
committed
Add support for passing arguments to constructors
- added InterfaceTraits type trait that can be used to define the constructor arguments used by the derived classes - changed class loaders to pass arguments to the derived classes based on the information stored in InterfaceTraits Signed-off-by: pum1k <55055380+pum1k@users.noreply.github.com>
1 parent 6e4c255 commit df46bba

10 files changed

Lines changed: 488 additions & 76 deletions

include/class_loader/class_loader.hpp

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include <memory>
4040
#include <mutex>
4141
#include <string>
42+
#include <utility>
4243
#include <vector>
4344

4445
// TODO(mikaelarguedas) remove this once console_bridge complies with this
@@ -119,13 +120,16 @@ class ClassLoader
119120
* if the library is not yet loaded (which typically happens when in "On Demand Load/Unload" mode).
120121
*
121122
* @param derived_class_name The name of the class we want to create (@see getAvailableClasses())
123+
* @param args Arguments for the constructor of the derived class (types defined
124+
* by InterfaceTraits of the Base class)
122125
* @return A std::shared_ptr<Base> to newly created plugin object
123126
*/
124-
template<class Base>
125-
std::shared_ptr<Base> createInstance(const std::string & derived_class_name)
127+
template<class Base, class ... Args,
128+
std::enable_if_t<is_interface_constructible_v<Base, Args...>, bool> = true>
129+
std::shared_ptr<Base> createInstance(const std::string & derived_class_name, Args &&... args)
126130
{
127131
return std::shared_ptr<Base>(
128-
createRawInstance<Base>(derived_class_name, true),
132+
createRawInstance<Base>(derived_class_name, true, std::forward<Args>(args)...),
129133
std::bind(&ClassLoader::onPluginDeletion<Base>, this, std::placeholders::_1)
130134
);
131135
}
@@ -141,12 +145,15 @@ class ClassLoader
141145
*
142146
* @param derived_class_name
143147
* The name of the class we want to create (@see getAvailableClasses()).
148+
* @param args Arguments for the constructor of the derived class (types defined
149+
* by InterfaceTraits of the Base class)
144150
* @return A std::unique_ptr<Base> to newly created plugin object.
145151
*/
146-
template<class Base>
147-
UniquePtr<Base> createUniqueInstance(const std::string & derived_class_name)
152+
template<class Base, class ... Args,
153+
std::enable_if_t<is_interface_constructible_v<Base, Args...>, bool> = true>
154+
UniquePtr<Base> createUniqueInstance(const std::string & derived_class_name, Args &&... args)
148155
{
149-
Base * raw = createRawInstance<Base>(derived_class_name, true);
156+
Base * raw = createRawInstance<Base>(derived_class_name, true, std::forward<Args>(args)...);
150157
return std::unique_ptr<Base, DeleterType<Base>>(
151158
raw,
152159
std::bind(&ClassLoader::onPluginDeletion<Base>, this, std::placeholders::_1)
@@ -164,12 +171,15 @@ class ClassLoader
164171
*
165172
* @param derived_class_name
166173
* The name of the class we want to create (@see getAvailableClasses()).
174+
* @param args Arguments for the constructor of the derived class (types defined
175+
* by InterfaceTraits of the Base class)
167176
* @return An unmanaged (i.e. not a shared_ptr) Base* to newly created plugin object.
168177
*/
169-
template<class Base>
170-
Base * createUnmanagedInstance(const std::string & derived_class_name)
178+
template<class Base, class ... Args,
179+
std::enable_if_t<is_interface_constructible_v<Base, Args...>, bool> = true>
180+
Base * createUnmanagedInstance(const std::string & derived_class_name, Args &&... args)
171181
{
172-
return createRawInstance<Base>(derived_class_name, false);
182+
return createRawInstance<Base>(derived_class_name, false, std::forward<Args>(args)...);
173183
}
174184

175185
/**
@@ -297,10 +307,13 @@ class ClassLoader
297307
* @param managed
298308
* If true, the returned pointer is assumed to be wrapped in a smart
299309
* pointer by the caller.
310+
* @param args Arguments for the constructor of the derived class (types defined
311+
* by InterfaceTraits of the Base class)
300312
* @return A Base* to newly created plugin object.
301313
*/
302-
template<class Base>
303-
Base * createRawInstance(const std::string & derived_class_name, bool managed)
314+
template<class Base, class ... Args,
315+
std::enable_if_t<is_interface_constructible_v<Base, Args...>, bool> = true>
316+
Base * createRawInstance(const std::string & derived_class_name, bool managed, Args &&... args)
304317
{
305318
if (!managed) {
306319
this->setUnmanagedInstanceBeenCreated(true);
@@ -324,7 +337,8 @@ class ClassLoader
324337
loadLibrary();
325338
}
326339

327-
Base * obj = class_loader::impl::createInstance<Base>(derived_class_name, this);
340+
Base * obj = class_loader::impl::createInstance<Base>(derived_class_name, this,
341+
std::forward<Args>(args)...);
328342
assert(obj != NULL); // Unreachable assertion if createInstance() throws on failure.
329343

330344
if (managed) {

include/class_loader/class_loader_core.hpp

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#endif
5656

5757
#include "class_loader/exceptions.hpp"
58+
#include "class_loader/interface_traits.hpp"
5859
#include "class_loader/meta_object.hpp"
5960
#include "class_loader/visibility_control.hpp"
6061

@@ -344,10 +345,13 @@ registerPlugin(const std::string & class_name, const std::string & base_class_na
344345
*
345346
* @param derived_class_name - The name of the derived class (unmangled)
346347
* @param loader - The ClassLoader whose scope we are within
348+
* @param args - Arguments for the constructor of the derived class (types defined
349+
* by InterfaceTraits of the Base class)
347350
* @return A pointer to newly created plugin, note caller is responsible for object destruction
348351
*/
349-
template<typename Base>
350-
Base * createInstance(const std::string & derived_class_name, ClassLoader * loader)
352+
template<typename Base, class ... Args,
353+
std::enable_if_t<is_interface_constructible_v<Base, Args...>, bool> = true>
354+
Base * createInstance(const std::string & derived_class_name, ClassLoader * loader, Args &&... args)
351355
{
352356
AbstractMetaObject<Base> * factory = nullptr;
353357

@@ -363,28 +367,26 @@ Base * createInstance(const std::string & derived_class_name, ClassLoader * load
363367

364368
Base * obj = nullptr;
365369
if (factory != nullptr && factory->isOwnedBy(loader)) {
366-
obj = factory->create();
370+
obj = factory->create(std::forward<Args>(args)...);
371+
} else if (factory && factory->isOwnedBy(nullptr)) {
372+
CONSOLE_BRIDGE_logDebug(
373+
"%s",
374+
"class_loader.impl: ALERT!!! "
375+
"A metaobject (i.e. factory) exists for desired class, but has no owner. "
376+
"This implies that the library containing the class was dlopen()ed by means other than "
377+
"through the class_loader interface. "
378+
"This can happen if you build plugin libraries that contain more than just plugins "
379+
"(i.e. normal code your app links against) -- that intrinsically will trigger a dlopen() "
380+
"prior to main(). "
381+
"You should isolate your plugins into their own library, otherwise it will not be "
382+
"possible to shutdown the library!");
383+
384+
obj = factory->create(std::forward<Args>(args)...);
367385
}
368386

369387
if (nullptr == obj) { // Was never created
370-
if (factory && factory->isOwnedBy(nullptr)) {
371-
CONSOLE_BRIDGE_logDebug(
372-
"%s",
373-
"class_loader.impl: ALERT!!! "
374-
"A metaobject (i.e. factory) exists for desired class, but has no owner. "
375-
"This implies that the library containing the class was dlopen()ed by means other than "
376-
"through the class_loader interface. "
377-
"This can happen if you build plugin libraries that contain more than just plugins "
378-
"(i.e. normal code your app links against) -- that intrinsically will trigger a dlopen() "
379-
"prior to main(). "
380-
"You should isolate your plugins into their own library, otherwise it will not be "
381-
"possible to shutdown the library!");
382-
383-
obj = factory->create();
384-
} else {
385-
throw class_loader::CreateClassException(
386-
"Could not create instance of type " + derived_class_name);
387-
}
388+
throw class_loader::CreateClassException(
389+
"Could not create instance of type " + derived_class_name);
388390
}
389391

390392
CONSOLE_BRIDGE_logDebug(
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Software License Agreement (BSD License)
3+
*
4+
* Copyright (c) 2025, Multi-robot Systems (MRS) group at Czech Technical University in Prague
5+
* All rights reserved.
6+
*
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* * Redistributions of source code must retain the above copyright
11+
* notice, this list of conditions and the following disclaimer.
12+
* * Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
* * Neither the name of the copyright holder nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
* POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
#ifndef CLASS_LOADER__INTERFACE_TRAITS_HPP_
33+
#define CLASS_LOADER__INTERFACE_TRAITS_HPP_
34+
35+
#include <type_traits>
36+
37+
namespace class_loader
38+
{
39+
40+
/**
41+
* @brief Customization point that allows setting additional properties of
42+
* an interface class.
43+
*
44+
* @tparam T Base class for which the traits are defined
45+
*
46+
* Users can crete specializations of this class for the base type for which
47+
* the properties should be set. Properties are optional and therefore might not
48+
* exist in a given specialization. To access these properties, use their
49+
* respective accessor traits which provide the default value when it is not
50+
* specified.
51+
*
52+
* Supported properties:
53+
* - `constructor_signature` (member type)
54+
* - accessor: interface_constructor_signature
55+
* - default: T()
56+
* - defines the signature with which will the class loader instantiate the
57+
* derived classes
58+
* - example:
59+
* \code
60+
* using constructor_signature = T(double, int);
61+
* \endcode
62+
* defines that derived classes of class T (replace with the base class type
63+
* used in this specialization) will be instantiated with `double` and `int`
64+
* as their constructor arguments
65+
*
66+
* Example:
67+
* \code
68+
* class BaseWithInterfaceCtor
69+
* {
70+
* public:
71+
* // constructor parameters for the base class do not need to match the derived classes
72+
* explicit BaseWithInterfaceCtor(std::string) {}
73+
* virtual ~BaseWithInterfaceCtor() = default;
74+
*
75+
* virtual int get_number() = 0;
76+
* };
77+
*
78+
* // Specialize the class_loader::InterfaceTraits struct to define properties of your interface
79+
* template<>
80+
* struct class_loader::InterfaceTraits<BaseWithInterfaceCtor>
81+
* {
82+
* // derived classes will be instantiated with two parameters (string and unique_ptr<int>)
83+
* using constructor_signature = BaseWithInterfaceCtor(std::string, std::unique_ptr<int>);
84+
* };
85+
* \endcode
86+
*/
87+
template<class T>
88+
struct InterfaceTraits
89+
{
90+
};
91+
92+
namespace impl
93+
{
94+
95+
template<class T, class = void>
96+
struct interface_constructor_signature_impl
97+
{
98+
using type = T();
99+
};
100+
101+
template<class T>
102+
struct interface_constructor_signature_impl<T,
103+
std::void_t<typename InterfaceTraits<T>::constructor_signature>>
104+
{
105+
using type = typename InterfaceTraits<T>::constructor_signature;
106+
};
107+
108+
} // namespace impl
109+
110+
/**
111+
* @brief Type trait for extracting the `constructor_signature` of
112+
* InterfaceTraits.
113+
*
114+
* @tparam T same as in InterfaceTraits
115+
*
116+
* Helper type:\n
117+
* @ref interface_constructor_signature_t<T> = interface_constructor_signature<T>::type;
118+
*/
119+
template<class T>
120+
struct interface_constructor_signature
121+
{
122+
/**
123+
* @brief InterfaceTraits<T>::constructor_signature if present, otherwise the
124+
* default value (see InterfaceTraits).
125+
*/
126+
using type = typename impl::interface_constructor_signature_impl<T>::type;
127+
};
128+
129+
/**
130+
* @brief Helper type alias @ref interface_constructor_signature<T>::type
131+
* @see interface_constructor_signature
132+
*/
133+
template<class T>
134+
using interface_constructor_signature_t =
135+
typename interface_constructor_signature<T>::type;
136+
137+
/**
138+
* @brief Type trait for checking whether plugins derived from T can be
139+
* constructed with specified arguments.
140+
*
141+
* @tparam Base same as in InterfaceTraits
142+
* @tparam Args list of arguments to check
143+
*
144+
* Contains static constexpr member variable `value` that is true if plugins
145+
* derived from `T` can be constructed using `Args`, false otherwise.
146+
*
147+
* Helper variable template:\n
148+
* @ref is_interface_constructible_v<T, Args...> =
149+
* @ref is_interface_constructible "is_interface_constructible<T, Args...>::value"
150+
*/
151+
template<class Base, class ... Args>
152+
struct is_interface_constructible
153+
: std::is_invocable<interface_constructor_signature_t<Base>, Args...>
154+
{
155+
};
156+
157+
/**
158+
* @brief Helper variable template for @ref is_interface_constructible "is_interface_constructible<T, Args...>::value"
159+
* @see is_interface_constructible
160+
*/
161+
template<class Base, class ... Args>
162+
constexpr bool is_interface_constructible_v =
163+
is_interface_constructible<Base, Args...>::value;
164+
165+
} // namespace class_loader
166+
167+
#endif // CLASS_LOADER__INTERFACE_TRAITS_HPP_

0 commit comments

Comments
 (0)