serialization: Implement JsonSerializer loading functionality

This commit is contained in:
Joshua Scott 2018-12-18 21:35:59 +00:00
parent f9ab6560d9
commit f3aa42578d
12 changed files with 303 additions and 23 deletions

View File

@ -34,6 +34,12 @@ namespace detail
template <> template <>
struct is_json_assignable<std::string> : std::true_type {}; struct is_json_assignable<std::string> : std::true_type {};
/**
* std::u16string can be assigned to a json object.
*/
template <>
struct is_json_assignable<std::u16string> : std::true_type {};
/** /**
* value_caster specialization for the generic case of casting * value_caster specialization for the generic case of casting
* any json-assignable value to a json object. * any json-assignable value to a json object.
@ -163,6 +169,34 @@ namespace detail
} }
}; };
/**
* Caster implementation for casting from json to any
* primitive type.
*/
template <typename DestT>
struct value_caster<nlohmann::json, DestT>
: value_caster_impl<nlohmann::json, DestT>
{
DestT cast_value(const nlohmann::json &value) const override
{
return value;
}
};
/**
* Caster implementation for casting from json to a
* std::string.
*/
template <>
struct value_caster<nlohmann::json, std::string>
: value_caster_impl<nlohmann::json, std::string>
{
std::string cast_value(const nlohmann::json &value) const override
{
return value;
}
};
/** /**
* A utility to call ValueCaster::declare<SrcT, DestT> with * A utility to call ValueCaster::declare<SrcT, DestT> with
* bi<N> and bui<N> as the destination type. * bi<N> and bui<N> as the destination type.

View File

@ -34,6 +34,8 @@ namespace pclass
virtual bool is_dynamic() const; virtual bool is_dynamic() const;
virtual Value get_value() const = 0; virtual Value get_value() const = 0;
virtual void set_value(Value value) = 0;
virtual const PropertyClass *get_object() const = 0; virtual const PropertyClass *get_object() const = 0;
virtual void set_object(std::unique_ptr<PropertyClass> &object) = 0; virtual void set_object(std::unique_ptr<PropertyClass> &object) = 0;
@ -70,12 +72,14 @@ namespace pclass
virtual void set_element_count(std::size_t size) = 0; virtual void set_element_count(std::size_t size) = 0;
Value get_value() const final override; Value get_value() const final override;
void set_value(Value value) final override;
const PropertyClass *get_object() const final override; const PropertyClass *get_object() const final override;
void set_object(std::unique_ptr<PropertyClass> &object) final override; void set_object(std::unique_ptr<PropertyClass> &object) final override;
void write_value_to(BitStream &stream) const final override; void write_value_to(BitStream &stream) const final override;
void read_value_from(BitStream &stream) final override; void read_value_from(BitStream &stream) final override;
virtual Value get_value(int index) const = 0; virtual Value get_value(int index) const = 0;
virtual void set_value(Value value, int index) = 0;
virtual const PropertyClass *get_object(int index) const = 0; virtual const PropertyClass *get_object(int index) const = 0;
virtual void set_object(std::unique_ptr<PropertyClass> &object, int index) = 0; virtual void set_object(std::unique_ptr<PropertyClass> &object, int index) = 0;
virtual void write_value_to(BitStream &stream, int index) const = 0; virtual void write_value_to(BitStream &stream, int index) const = 0;

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "ki/pclass/Property.h" #include "ki/pclass/Property.h"
#include "ki/pclass/PropertyClass.h"
#include "ki/util/exception.h" #include "ki/util/exception.h"
namespace ki namespace ki
@ -42,6 +43,13 @@ namespace pclass
return Value::make_reference<ValueT>(prop.m_value); return Value::make_reference<ValueT>(prop.m_value);
} }
static void set_value(StaticProperty<ValueT> &prop, Value value)
{
prop.m_value = value
.dereference<ValueT>()
.get<ValueT>();
}
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop) static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{ {
// ValueT does not derive from PropertyClass, and so, this property is not // ValueT does not derive from PropertyClass, and so, this property is not
@ -106,6 +114,13 @@ namespace pclass
return Value::make_reference<nonpointer_type>(*prop.m_value); return Value::make_reference<nonpointer_type>(*prop.m_value);
} }
static void set_value(StaticProperty<ValueT> &prop, Value value)
{
prop.m_value = value
.dereference<nonpointer_type>()
.take<nonpointer_type>();
}
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop) static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{ {
// ValueT does not derive from PropertyClass, and so, this property is not // ValueT does not derive from PropertyClass, and so, this property is not
@ -173,6 +188,13 @@ namespace pclass
return Value::make_reference<nonpointer_type>(*prop.m_value); return Value::make_reference<nonpointer_type>(*prop.m_value);
} }
static void set_value(StaticProperty<ValueT> &prop, Value value)
{
prop.m_value = value
.dereference<nonpointer_type>()
.take<nonpointer_type>();
}
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop) static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{ {
// ValueT does derive from PropertyClass, and we have a pointer to an instance // ValueT does derive from PropertyClass, and we have a pointer to an instance
@ -241,6 +263,13 @@ namespace pclass
return Value::make_reference<ValueT>(prop.m_value); return Value::make_reference<ValueT>(prop.m_value);
} }
static void set_value(StaticProperty<ValueT> &prop, Value value)
{
prop.m_value = value
.dereference<ValueT>()
.get<ValueT>();
}
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop) static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{ {
// ValueT does derive from PropertyClass, and we have an instance of ValueT, // ValueT does derive from PropertyClass, and we have an instance of ValueT,
@ -343,6 +372,11 @@ namespace pclass
return value_object_helper<ValueT>::get_value(prop); return value_object_helper<ValueT>::get_value(prop);
} }
static void set_value(StaticProperty<ValueT> &prop, Value value)
{
return value_object_helper<ValueT>::set_value(prop, value);
}
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop) static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{ {
return value_object_helper<ValueT>::get_object(prop); return value_object_helper<ValueT>::get_object(prop);
@ -414,6 +448,11 @@ namespace pclass
return value_helper<ValueT>::get_value(*this); return value_helper<ValueT>::get_value(*this);
} }
void set_value(Value value) override
{
value_helper<ValueT>::set_value(*this, value);
}
const PropertyClass *get_object() const override const PropertyClass *get_object() const override
{ {
return value_helper<ValueT>::get_object(*this); return value_helper<ValueT>::get_object(*this);

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <json.hpp>
#include "ki/pclass/HashCalculator.h" #include "ki/pclass/HashCalculator.h"
#include "ki/pclass/Casters.h" #include "ki/pclass/Casters.h"
#include "ki/pclass/types/Type.h" #include "ki/pclass/types/Type.h"
@ -32,6 +33,8 @@ namespace pclass
PrimitiveType<ValueT> &define_primitive(const std::string &name) PrimitiveType<ValueT> &define_primitive(const std::string &name)
{ {
detail::caster_declarer<ValueT>::declare(); detail::caster_declarer<ValueT>::declare();
ValueCaster::declare<nlohmann::json, ValueT>();
auto *type = new PrimitiveType<ValueT>(name, *this); auto *type = new PrimitiveType<ValueT>(name, *this);
define_type(std::unique_ptr<Type>( define_type(std::unique_ptr<Type>(
dynamic_cast<Type *>(type) dynamic_cast<Type *>(type)
@ -58,6 +61,8 @@ namespace pclass
CppEnumType<EnumT> &define_enum(const std::string &name) CppEnumType<EnumT> &define_enum(const std::string &name)
{ {
detail::caster_declarer<EnumT>::declare(); detail::caster_declarer<EnumT>::declare();
ValueCaster::declare<nlohmann::json, EnumT>();
auto *type = new CppEnumType<EnumT>(name, *this); auto *type = new CppEnumType<EnumT>(name, *this);
define_type(std::unique_ptr<Type>( define_type(std::unique_ptr<Type>(
dynamic_cast<Type *>(type) dynamic_cast<Type *>(type)

View File

@ -79,6 +79,11 @@ namespace pclass
* @param[in] ptr The pointer to deallocate. * @param[in] ptr The pointer to deallocate.
*/ */
virtual void deallocate(void *ptr) const = 0; virtual void deallocate(void *ptr) const = 0;
/**
* Create a copy of this deallocator.
*/
virtual value_deallocator_base *copy() const = 0;
}; };
/** /**
@ -92,18 +97,25 @@ namespace pclass
// By default, now that we have the proper type, just delete it. // By default, now that we have the proper type, just delete it.
delete static_cast<T *>(ptr); delete static_cast<T *>(ptr);
} }
value_deallocator_base *copy() const override
{
return new value_deallocator<T>();
}
}; };
/** /**
* TODO: Documentation * TODO: Documentation
*/ */
class ValueDeallocator class ValueDeallocator
{ {
// Allow Value to call make<T>() and the default constructor // Allow Value to call make<T>() and the default constructor
friend Value; friend Value;
public: public:
ValueDeallocator(ValueDeallocator &that);
ValueDeallocator(ValueDeallocator &&that) noexcept; ValueDeallocator(ValueDeallocator &&that) noexcept;
ValueDeallocator &operator=(ValueDeallocator &that);
ValueDeallocator &operator=(ValueDeallocator &&that) noexcept; ValueDeallocator &operator=(ValueDeallocator &&that) noexcept;
~ValueDeallocator(); ~ValueDeallocator();
@ -220,7 +232,9 @@ namespace pclass
class Value class Value
{ {
public: public:
Value(Value &that);
Value(Value &&that) noexcept; Value(Value &&that) noexcept;
Value &operator=(Value &that);
Value &operator=(Value &&that) noexcept; Value &operator=(Value &&that) noexcept;
~Value(); ~Value();

View File

@ -39,7 +39,15 @@ namespace pclass
// Ensure index is within bounds // Ensure index is within bounds
if (index < 0 || index >= prop.size()) if (index < 0 || index >= prop.size())
throw runtime_error("Index out of bounds."); throw runtime_error("Index out of bounds.");
return Value::make_reference(prop.at(index)); return Value::make_reference<ValueT>(prop.at(index));
}
static void set_value(VectorProperty<ValueT> &prop,
Value value, const int index)
{
prop.at(index) = value
.dereference<ValueT>()
.get<ValueT>();
} }
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index) static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
@ -79,6 +87,8 @@ namespace pclass
>::type >::type
> >
{ {
using nonpointer_type = typename std::remove_pointer<ValueT>::type;
static ValueT copy(VectorProperty<ValueT> &prop, const int index) static ValueT copy(VectorProperty<ValueT> &prop, const int index)
{ {
// Ensure index is within bounds // Ensure index is within bounds
@ -95,7 +105,15 @@ namespace pclass
// Ensure index is within bounds // Ensure index is within bounds
if (index < 0 || index >= prop.size()) if (index < 0 || index >= prop.size())
throw runtime_error("Index out of bounds."); throw runtime_error("Index out of bounds.");
return Value::make_reference(*prop.at(index)); return Value::make_reference<nonpointer_type>(*prop.at(index));
}
static void set_value(VectorProperty<ValueT> &prop,
Value value, const int index)
{
prop.at(index) = value
.dereference<nonpointer_type>()
.take<nonpointer_type>();
} }
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index) static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
@ -135,6 +153,8 @@ namespace pclass
>::type >::type
> >
{ {
using nonpointer_type = typename std::remove_pointer<ValueT>::type;
static ValueT copy(VectorProperty<ValueT> &prop, const int index) static ValueT copy(VectorProperty<ValueT> &prop, const int index)
{ {
// The copy constructor for all pointers is to copy the pointer // The copy constructor for all pointers is to copy the pointer
@ -147,7 +167,15 @@ namespace pclass
// Ensure index is within bounds // Ensure index is within bounds
if (index < 0 || index >= prop.size()) if (index < 0 || index >= prop.size())
throw runtime_error("Index out of bounds."); throw runtime_error("Index out of bounds.");
return Value::make_reference(*prop.at(index)); return Value::make_reference<nonpointer_type>(*prop.at(index));
}
static void set_value(VectorProperty<ValueT> &prop,
Value value, const int index)
{
prop.at(index) = value
.dereference<nonpointer_type>()
.take<nonpointer_type>();
} }
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index) static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
@ -214,7 +242,15 @@ namespace pclass
// Ensure index is within bounds // Ensure index is within bounds
if (index < 0 || index >= prop.size()) if (index < 0 || index >= prop.size())
throw runtime_error("Index out of bounds."); throw runtime_error("Index out of bounds.");
return Value::make_reference(prop.at(index)); return Value::make_reference<ValueT>(prop.at(index));
}
static void set_value(VectorProperty<ValueT> &prop,
Value value, const int index)
{
prop.at(index) = value
.dereference<ValueT>()
.get<ValueT>();
} }
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index) static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
@ -326,18 +362,24 @@ namespace pclass
return vector_value_object_helper<ValueT>::copy(prop, index); return vector_value_object_helper<ValueT>::copy(prop, index);
} }
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop,
const int index)
{
return vector_value_object_helper<ValueT>::get_object(prop, index);
}
static Value get_value(const VectorProperty<ValueT> &prop, static Value get_value(const VectorProperty<ValueT> &prop,
const int index) const int index)
{ {
return vector_value_object_helper<ValueT>::get_value(prop, index); return vector_value_object_helper<ValueT>::get_value(prop, index);
} }
static void set_value(VectorProperty<ValueT> &prop,
Value value, const int index)
{
vector_value_object_helper<ValueT>::set_value(prop, value, index);
}
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop,
const int index)
{
return vector_value_object_helper<ValueT>::get_object(prop, index);
}
static void set_object(VectorProperty<ValueT> &prop, static void set_object(VectorProperty<ValueT> &prop,
std::unique_ptr<PropertyClass> &object, const int index) std::unique_ptr<PropertyClass> &object, const int index)
{ {
@ -405,6 +447,13 @@ namespace pclass
return vector_value_helper<ValueT>::get_value(*this, index); return vector_value_helper<ValueT>::get_value(*this, index);
} }
void set_value(Value value, int index) override
{
if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds.");
return vector_value_helper<ValueT>::set_value(*this, value, index);
}
const PropertyClass *get_object(const int index) const override const PropertyClass *get_object(const int index) const override
{ {
return vector_value_helper<ValueT>::get_object(*this, index); return vector_value_helper<ValueT>::get_object(*this, index);

View File

@ -30,6 +30,11 @@ namespace serialization
nlohmann::json save_object(const pclass::PropertyClass *object) const; nlohmann::json save_object(const pclass::PropertyClass *object) const;
void save_property(nlohmann::json &j, const pclass::IProperty &prop) const; void save_property(nlohmann::json &j, const pclass::IProperty &prop) const;
void save_dynamic_property(nlohmann::json &j, const pclass::IDynamicProperty &prop) const; void save_dynamic_property(nlohmann::json &j, const pclass::IDynamicProperty &prop) const;
virtual bool preload_object(std::unique_ptr<pclass::PropertyClass> &dest, nlohmann::json &j) const;
void load_object(std::unique_ptr<pclass::PropertyClass> &dest, nlohmann::json &j) const;
void load_property(pclass::IProperty &prop, nlohmann::json &j) const;
void load_dynamic_property(pclass::IDynamicProperty &prop, nlohmann::json &j) const;
}; };
} }
} }

View File

@ -92,6 +92,12 @@ namespace pclass
throw runtime_error("Called get_value() on a dynamic property. Use get_value(index) instead."); throw runtime_error("Called get_value() on a dynamic property. Use get_value(index) instead.");
} }
void IDynamicProperty::set_value(Value value)
{
// The caller must specify an index
throw runtime_error("Called set_value() on a dynamic property. Use set_value(index) instead.");
}
const PropertyClass *IDynamicProperty::get_object() const const PropertyClass *IDynamicProperty::get_object() const
{ {
// The caller must specify an index // The caller must specify an index

View File

@ -24,12 +24,23 @@ namespace pclass
m_deallocator = deallocator; m_deallocator = deallocator;
} }
ValueDeallocator::ValueDeallocator(ValueDeallocator &that)
{
m_deallocator = that.m_deallocator->copy();
}
ValueDeallocator::ValueDeallocator(ValueDeallocator &&that) noexcept ValueDeallocator::ValueDeallocator(ValueDeallocator &&that) noexcept
{ {
m_deallocator = that.m_deallocator; m_deallocator = that.m_deallocator;
that.m_deallocator = nullptr; that.m_deallocator = nullptr;
} }
ValueDeallocator &ValueDeallocator::operator=(ValueDeallocator &that)
{
m_deallocator = that.m_deallocator->copy();
return *this;
}
ValueDeallocator& ValueDeallocator::operator=(ValueDeallocator &&that) noexcept ValueDeallocator& ValueDeallocator::operator=(ValueDeallocator &&that) noexcept
{ {
m_deallocator = that.m_deallocator; m_deallocator = that.m_deallocator;
@ -76,6 +87,15 @@ namespace pclass
m_deallocator = detail::ValueDeallocator(); m_deallocator = detail::ValueDeallocator();
} }
Value::Value(Value& that)
{
m_value_ptr = that.m_value_ptr;
m_ptr_is_owned = false;
m_type_hash = that.m_type_hash;
m_caster = that.m_caster;
m_deallocator = that.m_deallocator;
}
Value::Value(Value &&that) noexcept Value::Value(Value &&that) noexcept
{ {
// Move pointer to this Value object, and take ownership if // Move pointer to this Value object, and take ownership if
@ -88,6 +108,17 @@ namespace pclass
m_deallocator = std::move(that.m_deallocator); m_deallocator = std::move(that.m_deallocator);
} }
Value &Value::operator=(Value &that)
{
m_value_ptr = that.m_value_ptr;
m_ptr_is_owned = false;
m_type_hash = that.m_type_hash;
m_caster = that.m_caster;
m_deallocator = that.m_deallocator;
return *this;
}
Value &Value::operator=(Value &&that) noexcept Value &Value::operator=(Value &&that) noexcept
{ {
// If the current pointer is owned, deallocate it // If the current pointer is owned, deallocate it

View File

@ -435,6 +435,5 @@ namespace serialization
prop.read_value_from(stream, i); prop.read_value_from(stream, i);
} }
} }
} }
} }

View File

@ -1,4 +1,5 @@
#include "ki/serialization/JsonSerializer.h" #include "ki/serialization/JsonSerializer.h"
#include <sstream>
using namespace nlohmann; using namespace nlohmann;
@ -54,7 +55,6 @@ namespace serialization
{ {
if (prop.is_dynamic()) if (prop.is_dynamic())
{ {
// Cast the property to a IDynamicProperty
const auto &dynamic_property = const auto &dynamic_property =
dynamic_cast<const pclass::IDynamicProperty &>(prop); dynamic_cast<const pclass::IDynamicProperty &>(prop);
return save_dynamic_property(j, dynamic_property); return save_dynamic_property(j, dynamic_property);
@ -94,12 +94,108 @@ namespace serialization
j[prop.get_name()] = property_value; j[prop.get_name()] = property_value;
} }
void JsonSerializer::load(std::unique_ptr<pclass::PropertyClass>& dest, void JsonSerializer::load(std::unique_ptr<pclass::PropertyClass> &dest,
const std::string& json_string) const const std::string &json_string) const
{ {
// TODO: JSON Deserialization try
auto j = json::parse(json_string); {
dest = nullptr; auto j = json::parse(json_string);
load_object(dest, j);
}
catch (json::exception &e)
{
std::ostringstream oss;
oss << "Failed to deserialize JSON - " << e.what();
throw runtime_error(oss.str());
}
}
bool JsonSerializer::preload_object(
std::unique_ptr<pclass::PropertyClass>& dest, json& j) const
{
// If meta information is not present, assume that the type hash is null.
if (!j.count("_pclass_meta"))
dest = nullptr;
else
{
// Use the type hash to instantiate an object
const auto type_hash = j["_pclass_meta"].value("type_hash", NULL);
if (type_hash != 0)
{
const auto &type = m_type_system->get_type(type_hash);
dest = type.instantiate();
}
else
dest = nullptr;
}
return dest != nullptr;
}
void JsonSerializer::load_object(
std::unique_ptr<pclass::PropertyClass> &dest, json &j) const
{
if (!preload_object(dest, j))
return;
auto &property_list = dest->get_properties();
for (auto it = property_list.begin();
it != property_list.end(); ++it)
{
auto &prop = *it;
load_property(prop, j);
}
}
void JsonSerializer::load_property(
pclass::IProperty &prop, json &j) const
{
if (!j.count(prop.get_name()))
{
std::ostringstream oss;
oss << "JSON object missing property: '" << prop.get_name() << "' "
<< "(type='" << prop.get_instance().get_type().get_name() << "').";
throw runtime_error(oss.str());
}
auto &property_j = j[prop.get_name()];
if (prop.is_dynamic())
{
auto &dynamic_property =
dynamic_cast<pclass::IDynamicProperty &>(prop);
return load_dynamic_property(dynamic_property, j);
}
if (prop.get_type().get_kind() == pclass::Type::kind::CLASS)
{
std::unique_ptr<pclass::PropertyClass> other_object = nullptr;
load_object(other_object, property_j);
prop.set_object(other_object);
}
else
prop.set_value(
pclass::Value::make_reference<json>(property_j)
);
}
void JsonSerializer::load_dynamic_property(
pclass::IDynamicProperty &prop, json &j) const
{
auto &property_j = j[prop.get_name()];
prop.set_element_count(property_j.size());
for (auto i = 0; i < prop.get_element_count(); ++i)
{
if (prop.get_type().get_kind() == pclass::Type::kind::CLASS)
{
std::unique_ptr<pclass::PropertyClass> other_object = nullptr;
load_object(other_object, property_j.at(i));
prop.set_object(other_object, i);
}
else
prop.set_value(
pclass::Value::make_reference<json>(property_j.at(i)), i
);
}
} }
} }
} }

View File

@ -108,7 +108,7 @@ namespace detail
struct value_caster<Vector3D, nlohmann::json> struct value_caster<Vector3D, nlohmann::json>
: value_caster_impl<Vector3D, nlohmann::json> : value_caster_impl<Vector3D, nlohmann::json>
{ {
nlohmann::json cast_value(const Vector3D &value) const nlohmann::json cast_value(const Vector3D &value) const override
{ {
return { return {
{ "x", value.m_x }, { "x", value.m_x },
@ -125,7 +125,7 @@ namespace detail
struct value_caster<nlohmann::json, Vector3D> struct value_caster<nlohmann::json, Vector3D>
: value_caster_impl<nlohmann::json, Vector3D> : value_caster_impl<nlohmann::json, Vector3D>
{ {
Vector3D cast_value(const nlohmann::json &value) const Vector3D cast_value(const nlohmann::json &value) const override
{ {
return Vector3D( return Vector3D(
value["x"].get<float>(), value["x"].get<float>(),
@ -562,13 +562,11 @@ void test_serializer(
serializer.load(object, sample); serializer.load(object, sample);
// Set test_object so that it is validated by the caller // Set test_object so that it is validated by the caller
/*
REQUIRE(object != nullptr); REQUIRE(object != nullptr);
test_object = std::unique_ptr<TestObject>( test_object = std::unique_ptr<TestObject>(
dynamic_cast<TestObject *>(object.release()) dynamic_cast<TestObject *>(object.release())
); );
REQUIRE(test_object != nullptr); REQUIRE(test_object != nullptr);
*/
} }
} }