Skip to content

Commit 5c27948

Browse files
authored
Add support for passing arguments to constructors (#223)
- 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 5c27948

10 files changed

Lines changed: 533 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: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
* Software License Agreement (BSD License)
3+
*
4+
* Copyright (c) 2026, 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+
namespace impl
41+
{
42+
43+
template<class ...>
44+
constexpr bool false_v = false;
45+
46+
} // namespace impl
47+
48+
template<class ... Params>
49+
struct ConstructorParameters {};
50+
51+
/**
52+
* @brief Customization point that allows setting additional properties of
53+
* an interface class.
54+
*
55+
* @tparam T Base class for which the traits are defined
56+
*
57+
* Users can crete specializations of this class for the base type for which
58+
* the properties should be set. Properties are optional and therefore might not
59+
* exist in a given specialization. To access these properties, use their
60+
* respective accessor traits which provide the default value when it is not
61+
* specified.
62+
*
63+
* Supported properties:
64+
* - `constructor_parameters` (member type)
65+
* - accessor: interface_constructor_parameters
66+
* - default: ConstructorParameters<>
67+
* - defines the parameters with which will the class loader instantiate the
68+
* derived classes
69+
* - example:
70+
* \code
71+
* using constructor_parameters = ConstructorParameters<double, int>;
72+
* \endcode
73+
* defines that derived classes of class T (replace with the base class type
74+
* used in this specialization) will be instantiated with `double` and `int`
75+
* as their constructor arguments
76+
*
77+
* Example:
78+
* \code
79+
* class BaseWithInterfaceCtor
80+
* {
81+
* public:
82+
* // constructor parameters for the base class do not need to match the derived classes
83+
* explicit BaseWithInterfaceCtor(std::string) {}
84+
* virtual ~BaseWithInterfaceCtor() = default;
85+
*
86+
* virtual int get_number() = 0;
87+
* };
88+
*
89+
* // Specialize the class_loader::InterfaceTraits struct to define properties of your interface
90+
* template<>
91+
* struct class_loader::InterfaceTraits<BaseWithInterfaceCtor>
92+
* {
93+
* // derived classes will be instantiated with two parameters (string and unique_ptr<int>)
94+
* using constructor_parameters = ConstructorParameters<std::string, std::unique_ptr<int>>;
95+
* };
96+
* \endcode
97+
*/
98+
template<class T>
99+
struct InterfaceTraits
100+
{
101+
};
102+
103+
namespace impl
104+
{
105+
106+
template<class T, class = void>
107+
struct interface_constructor_parameters_impl
108+
{
109+
using type = ConstructorParameters<>;
110+
};
111+
112+
template<class T>
113+
struct interface_constructor_parameters_impl<T,
114+
std::void_t<typename InterfaceTraits<T>::constructor_parameters>>
115+
{
116+
using type = typename InterfaceTraits<T>::constructor_parameters;
117+
};
118+
119+
} // namespace impl
120+
121+
/**
122+
* @brief Type trait for extracting the `constructor_parameters` of
123+
* InterfaceTraits.
124+
*
125+
* @tparam T same as in InterfaceTraits
126+
*
127+
* Helper type:\n
128+
* @ref interface_constructor_parameters_t<T> = interface_constructor_parameters<T>::type;
129+
*/
130+
template<class T>
131+
struct interface_constructor_parameters
132+
{
133+
/**
134+
* @brief InterfaceTraits<T>::constructor_parameters if present, otherwise the
135+
* default value (see InterfaceTraits).
136+
*/
137+
using type = typename impl::interface_constructor_parameters_impl<T>::type;
138+
};
139+
140+
/**
141+
* @brief Helper type alias @ref interface_constructor_parameters<T>::type
142+
* @see interface_constructor_parameters
143+
*/
144+
template<class T>
145+
using interface_constructor_parameters_t =
146+
typename interface_constructor_parameters<T>::type;
147+
148+
namespace impl
149+
{
150+
151+
template<class ... Ts>
152+
struct is_interface_constructible_impl
153+
{
154+
static_assert(false_v<Ts...>, "Base template selected.");
155+
};
156+
157+
template<class ... Params, class ... Args>
158+
struct is_interface_constructible_impl<ConstructorParameters<Params...>, Args...>
159+
: std::is_invocable<void(Params...), Args...>
160+
{
161+
};
162+
163+
} // namespace impl
164+
165+
/**
166+
* @brief Type trait for checking whether plugins derived from T can be
167+
* constructed with specified arguments.
168+
*
169+
* @tparam Base same as in InterfaceTraits
170+
* @tparam Args list of arguments to check
171+
*
172+
* Contains static constexpr member variable `value` that is true if plugins
173+
* derived from `T` can be constructed using `Args`, false otherwise.
174+
*
175+
* Helper variable template:\n
176+
* @ref is_interface_constructible_v<T, Args...> =
177+
* @ref is_interface_constructible "is_interface_constructible<T, Args...>::value"
178+
*/
179+
template<class Base, class ... Args>
180+
struct is_interface_constructible
181+
: impl::is_interface_constructible_impl<interface_constructor_parameters_t<Base>, Args...>
182+
{
183+
};
184+
185+
/**
186+
* @brief Helper variable template for @ref is_interface_constructible "is_interface_constructible<T, Args...>::value"
187+
* @see is_interface_constructible
188+
*/
189+
template<class Base, class ... Args>
190+
constexpr bool is_interface_constructible_v =
191+
is_interface_constructible<Base, Args...>::value;
192+
193+
} // namespace class_loader
194+
195+
#endif // CLASS_LOADER__INTERFACE_TRAITS_HPP_

0 commit comments

Comments
 (0)