xo-alloc2: scaffold for interface+data separation
This commit is contained in:
parent
704c870c1f
commit
382b2d2a2c
4 changed files with 298 additions and 31 deletions
107
README.md
107
README.md
|
|
@ -3,13 +3,15 @@
|
||||||
# Relative to xo-alloc:
|
# Relative to xo-alloc:
|
||||||
|
|
||||||
1. keep interface and data separate.
|
1. keep interface and data separate.
|
||||||
1a. `Representation` classes. Entirely passive; strictly no methods.
|
1a. *Representation* or *Data* classes. Entirely passive; strictly no methods.
|
||||||
motivation: data doesn't carry any linker-dependency baggage;
|
motivation: data doesn't carry any linker-dependency baggage;
|
||||||
it's just layout.
|
it's just layout.
|
||||||
|
|
||||||
example:
|
example:
|
||||||
struct RPolar { double arg; double mag; };
|
```
|
||||||
struct RRect { double x; double y; };
|
struct DPolar { double arg; double mag; };
|
||||||
|
struct DRRect { double x; double y; };
|
||||||
|
```
|
||||||
|
|
||||||
1b. `Interface` classes. These have abstract methods only.
|
1b. `Interface` classes. These have abstract methods only.
|
||||||
motivation: for runtime polymorphism, specify interface
|
motivation: for runtime polymorphism, specify interface
|
||||||
|
|
@ -18,7 +20,8 @@
|
||||||
as first argument.
|
as first argument.
|
||||||
|
|
||||||
example:
|
example:
|
||||||
struct IComplex {
|
```
|
||||||
|
struct AComplex {
|
||||||
using repr_type = void;
|
using repr_type = void;
|
||||||
|
|
||||||
virtual double xcoord(void * repr) const = 0;
|
virtual double xcoord(void * repr) const = 0;
|
||||||
|
|
@ -26,6 +29,7 @@
|
||||||
virtual double magnitude(void * repr) const = 0;
|
virtual double magnitude(void * repr) const = 0;
|
||||||
virtual double argument(void * repr) const = 0;
|
virtual double argument(void * repr) const = 0;
|
||||||
};
|
};
|
||||||
|
```
|
||||||
|
|
||||||
1c. `Implementation` classes. Implement a specific interface (as in 1b)
|
1c. `Implementation` classes. Implement a specific interface (as in 1b)
|
||||||
for a specific data representation (as in 1a).
|
for a specific data representation (as in 1a).
|
||||||
|
|
@ -36,7 +40,7 @@
|
||||||
|
|
||||||
example:
|
example:
|
||||||
```
|
```
|
||||||
struct Complex_Rect {
|
struct IComplex_Rect : public AComplex {
|
||||||
using repr_type = RRect;
|
using repr_type = RRect;
|
||||||
|
|
||||||
double _xcoord(RRect * repr) const { return repr->x; }
|
double _xcoord(RRect * repr) const { return repr->x; }
|
||||||
|
|
@ -60,7 +64,7 @@
|
||||||
double argument(void * repr) const final override;
|
double argument(void * repr) const final override;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Complex_Polar {
|
struct IComplex_Polar : public AComplex {
|
||||||
using repr_type = RPolar;
|
using repr_type = RPolar;
|
||||||
|
|
||||||
// implement IComplex for RPolar
|
// implement IComplex for RPolar
|
||||||
|
|
@ -68,70 +72,111 @@
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Here `IComplex_Rect` and `IComplex_Polar` are constructible.
|
||||||
|
They're concrete in the sense that they expect a specific representation
|
||||||
|
(`IComplex_Rect::repr_type`, `IComplex_Polar::repr_type` respectively).
|
||||||
|
|
||||||
1d. `Object` classes. Pair implementation and interface.
|
1d. `Object` classes. Pair implementation and interface.
|
||||||
May use smart pointer here to express strategy for managing
|
May use smart pointer here to express strategy for managing
|
||||||
memory used for representation. Don't expect to need this for
|
memory used for representation. Don't expect to need this for
|
||||||
interfaces, since interface content entirely known at compile time.
|
interfaces, since interface content entirely known at compile time.
|
||||||
|
|
||||||
example:
|
example:
|
||||||
|
```
|
||||||
// borrowed
|
// borrowed
|
||||||
struct _Complex_Rect : public Complex_Rect {
|
struct OComplex_Rect : public IComplex_Rect {
|
||||||
bp<RRect> repr; // naked pointer
|
DRect * data() const { return data_; }
|
||||||
|
|
||||||
|
bp<DRect> data_; // naked pointer
|
||||||
};
|
};
|
||||||
|
|
||||||
struct _Complex_Polar : public Complex_Polar {
|
struct OComplex_Polar : public IComplex_Polar {
|
||||||
bp<RPolar> repr;
|
DPolar * data() const { return data_; }
|
||||||
|
|
||||||
|
bp<DPolar> data_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// unique
|
// unique
|
||||||
struct _Complex_Rect : public Complex_Rect {
|
struct OComplex_Rect : public IComplex_Rect {
|
||||||
up<RRect> repr; // unique_ptr
|
DRect * data() const { return data_; }
|
||||||
|
|
||||||
|
up<DRect> data_; // unique_ptr
|
||||||
};
|
};
|
||||||
|
|
||||||
..
|
..
|
||||||
|
```
|
||||||
Can do this generically.
|
Can do this generically.
|
||||||
|
|
||||||
|
```
|
||||||
|
// in bx: 'b' short for 'borrowed' as in unowned.
|
||||||
|
// 'x' just to distinguish from 'pointer'.
|
||||||
|
//
|
||||||
template <typename Iface,
|
template <typename Iface,
|
||||||
typename Repr = typename Iface::repr_type>
|
typename Repr = typename Iface::repr_type>
|
||||||
struct bxp : public Iface {
|
struct bx : public Iface {
|
||||||
|
explicit bx(Repr * data) : data_{data} {}
|
||||||
|
Repr * data() const { return data_; }
|
||||||
|
|
||||||
bp<Repr> data_;
|
bp<Repr> data_;
|
||||||
};
|
};
|
||||||
|
|
||||||
using t1 = bxp<Complex_Rect>;
|
DRect z1_data{1.0, -1.0};
|
||||||
using t2 = bxp<Complex_Polar>;
|
bx<IComplex_Rect> z1{&z1_data};
|
||||||
|
|
||||||
etc.
|
|
||||||
|
|
||||||
|
DPolar z2_data{sqrt(2.0), pi * 8/7};
|
||||||
|
bx<IComplex_Polar> z2{&z2_data};
|
||||||
|
```
|
||||||
Then to invoke a method (compile-time polymorphism)
|
Then to invoke a method (compile-time polymorphism)
|
||||||
|
```
|
||||||
bxp<Complex_Rect> obj;
|
z1._xcoord(z1.data());
|
||||||
obj.xcoord(obj.data_); // obj.xcoord()
|
```
|
||||||
|
|
||||||
Or for runtime polymorphism
|
|
||||||
|
|
||||||
bxp<IComplex> obj;
|
|
||||||
obj.xcoord(obj.data_); // obj.xcoord()
|
|
||||||
|
|
||||||
1e. Runtime polymorphism
|
1e. Runtime polymorphism
|
||||||
|
|
||||||
Observe that bxp<Complex_Rect> and bxp<Complex_Polar> have the same
|
Observe that bxp<Complex_Rect> and bxp<Complex_Polar> have the same
|
||||||
top-level representation.
|
top-level representation.
|
||||||
|
|
||||||
- Both have iface member that inherits IComplex,
|
- Both have iface member that inherits IComplex,
|
||||||
- both have data pointer compatible with their respective iface member
|
- both have data pointer compatible with their respective iface member
|
||||||
|
|
||||||
Can have common representation for runtime polymorphism
|
Can have common representation for runtime polymorphism
|
||||||
|
|
||||||
- `bxp<Complex_Rect>` and `bxp<Complex_Polar>` have the same size
|
- `bxp<Complex_Rect>` and `bxp<Complex_Polar>` have the same size
|
||||||
and compatible representation.
|
and compatible representation.
|
||||||
- both inherit `IComplex`
|
- both inherit `IComplex`
|
||||||
|
- safe to reinterpret cast
|
||||||
|
```
|
||||||
|
// type-erased (placeholder, never used)
|
||||||
|
struct IComplex_Any : public AComplex {
|
||||||
|
using repr_type = void;
|
||||||
|
|
||||||
- safe to reinterpret cast to
|
double xcoord(void * repr) const final override { assert(false); return 0.0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
bx<IComplex_Rect> z1 = ...;
|
||||||
|
bx<IComplex_Any> z1_any = reinterpret_cast<IComplex_Any>(z1);
|
||||||
|
```
|
||||||
|
Capturing the pattern:
|
||||||
|
```
|
||||||
|
// in abstract interface
|
||||||
|
struct AComplex {
|
||||||
|
using ErasedIfaceType = IComplex_Any;
|
||||||
|
..
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Iface,
|
||||||
|
typename Repr = typename Iface::repr_type>
|
||||||
|
struct bx : public Iface {
|
||||||
|
..
|
||||||
|
operator bx<Iface::typename ErasedIfaceType>() {
|
||||||
|
// in particular, overwrites vtable pointer
|
||||||
|
return reinterpret_cast<bx<Iface::typename ErasedIfaceType>>(*this);
|
||||||
|
}
|
||||||
|
..
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
2. Remarks
|
2. Remarks
|
||||||
- shared pattern with pimpl idiom,
|
- shared pattern with pimpl idiom,
|
||||||
except impl isn't private
|
except impl isn't private
|
||||||
|
- can use the same Data type with an unrelated interface.
|
||||||
|
Although lose the automatic assocation
|
||||||
- can put forwarding methods into object structs,
|
- can put forwarding methods into object structs,
|
||||||
though will be boilerplatey.
|
though will be boilerplatey.
|
||||||
|
|
||||||
|
|
|
||||||
14
utest/CMakeLists.txt
Normal file
14
utest/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# xo-alloc2/utest/CMakeLists.txt
|
||||||
|
#
|
||||||
|
|
||||||
|
set(UTEST_EXE utest.alloc2)
|
||||||
|
set(UTEST_SRCS
|
||||||
|
alloc2_utest_main.cpp
|
||||||
|
objectmodel.test.cpp)
|
||||||
|
|
||||||
|
if (ENABLE_TESTING)
|
||||||
|
xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS})
|
||||||
|
xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# end CMakeLists.txt
|
||||||
6
utest/alloc2_utest_main.cpp
Normal file
6
utest/alloc2_utest_main.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/* file alloc2_utest_main.cpp */
|
||||||
|
|
||||||
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
|
||||||
|
/* end alloc2_utest_main.cpp */
|
||||||
202
utest/objectmodel.test.cpp
Normal file
202
utest/objectmodel.test.cpp
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
/** @file objectmodel.test.cpp
|
||||||
|
*
|
||||||
|
* @author: Roland Conybeare, Dec 2025
|
||||||
|
*
|
||||||
|
* Testing rust-like split iface/data object model
|
||||||
|
* See xo-alloc2/README.md
|
||||||
|
*
|
||||||
|
* Ingredients:
|
||||||
|
* 1. abstract interface: all virtual methods. No assumptions about representation.
|
||||||
|
* No state (besides implict vtable pointer)
|
||||||
|
*
|
||||||
|
* Rules:
|
||||||
|
* 1. abstract interface must have no state besides implicit vtable pointer.
|
||||||
|
* This is a strongly-held principle, we're keeping data representation entirely
|
||||||
|
* separate
|
||||||
|
* 2. representations as passive as possible. No getters. All public members.
|
||||||
|
* Exceptions to this principle:
|
||||||
|
* - ctors (including copy/move ctors, when needed)
|
||||||
|
* - dtors
|
||||||
|
*
|
||||||
|
* Conventions:
|
||||||
|
* 1. abstract interface start with letter A, e.g. AComplex
|
||||||
|
* 2. representation struct names follow pattern DRepr, e.g. DPolar, DRect.
|
||||||
|
* Don't require "intended primary interface" in the name,
|
||||||
|
* since we're seeking ability to attach the same data to different interfaces
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include <catch2/catch.hpp>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
namespace ut {
|
||||||
|
namespace {
|
||||||
|
/** Associates an interface with an representation.
|
||||||
|
* Specialize to record such associations.
|
||||||
|
**/
|
||||||
|
|
||||||
|
template <typename Interface,
|
||||||
|
typename Data>
|
||||||
|
struct ISpecificFor;
|
||||||
|
|
||||||
|
/** type-erased implementation of AComplex, see below **/
|
||||||
|
struct IComplex_Any;
|
||||||
|
|
||||||
|
/** abstract interface for a complex number **/
|
||||||
|
struct AComplex {
|
||||||
|
using TypeErasedIface = IComplex_Any;
|
||||||
|
|
||||||
|
virtual double xcoord(void * data) const = 0;
|
||||||
|
virtual double ycoord(void * data) const = 0;
|
||||||
|
virtual double argument(void * data) const = 0;
|
||||||
|
virtual double magnitude(void * data) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** type-erased implementation of AComplex, for runtime polymorphism
|
||||||
|
* Usable by (and only by) overwriting with a typed implementation,
|
||||||
|
* such as IComplex_RectCoords or IComplex_PolarCoords.
|
||||||
|
**/
|
||||||
|
struct IComplex_Any : public AComplex {
|
||||||
|
virtual double xcoord(void *) const final override { assert(false); return 0.0; }
|
||||||
|
virtual double ycoord(void *) const final override { assert(false); return 0.0; }
|
||||||
|
virtual double argument(void *) const final override { assert(false); return 0.0; }
|
||||||
|
virtual double magnitude(void *) const final override { assert(false); return 0.0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Repr>
|
||||||
|
struct IComplex_Specific : public AComplex {
|
||||||
|
double _xcoord(Repr *) const;
|
||||||
|
double _ycoord(Repr *) const;
|
||||||
|
double _argument(Repr *) const;
|
||||||
|
double _magnitude(Repr *) const;
|
||||||
|
|
||||||
|
virtual double xcoord(void * data) const final override { return _xcoord((Repr*)data); }
|
||||||
|
virtual double ycoord(void * data) const final override { return _ycoord((Repr*)data); }
|
||||||
|
virtual double argument(void * data) const final override { return _argument((Repr*)data); }
|
||||||
|
virtual double magnitude(void * data) const final override { return _magnitude((Repr*)data); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----- Polar Coordinates -----
|
||||||
|
|
||||||
|
/** complex number, represented using polar coordinates **/
|
||||||
|
struct DPolarCoords {
|
||||||
|
DPolarCoords(double arg, double mag) : arg_{arg}, mag_{mag} {}
|
||||||
|
|
||||||
|
double arg_;
|
||||||
|
double mag_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** implementation of AComplex interface with representation DPolarCoords **/
|
||||||
|
using struct IComplex_DPolarCoords = IComplex_Specific<DPolarCoords>;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
IComplex_Specific<DPolarCoords>::_xcoord(DPolarCoords * data) const {
|
||||||
|
return data->mag_ * std::cos(data->arg_);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
IComplex_Specific<DPolarCoords>::_ycoord(DPolarCoords * data) const {
|
||||||
|
return data->mag_ * std::sin(data->arg_);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
IComplex_Specific<DPolarCoords>::_argument(DPolarCoords * data) const {
|
||||||
|
return data->arg_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
IComplex_Specific<DPolarCoords>::_magnitude(DPolarCoords * data) const {
|
||||||
|
return data->mag_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct ISpecificFor<AComplex, DPolarCoords> {
|
||||||
|
using ImplType = IComplex_Specific<DPolarCoords>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----- Rectangular Coordinates -----
|
||||||
|
|
||||||
|
/** complex number, represented using rectangular coordinates **/
|
||||||
|
struct DRectCoords {
|
||||||
|
DRectCoords(double x, double y) : x_{x}, y_{y} {}
|
||||||
|
|
||||||
|
double x_;
|
||||||
|
double y_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** implementation of AComplex interface with representation DRectCoords **/
|
||||||
|
using struct IComplex_DRectCoords = IComplex_Specific<DRectCoords>;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
IComplex_Specific<DRectCoords>::_xcoord(DRectCoords * data) const {
|
||||||
|
return data->mag_ * std::cos(data->arg_);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
IComplex_Specific<DRectCoords>::_ycoord(DRectCoords * data) const {
|
||||||
|
return data->mag_ * std::sin(data->arg_);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
IComplex_Specific<DRectCoords>::_argument(DRectCoords * data) const {
|
||||||
|
return data->arg_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
IComplex_Specific<DRectCoords>::_magnitude(DRectCoords * data) const {
|
||||||
|
return data->mag_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct ISpecificFor<AComplex, DRectCoords> {
|
||||||
|
using ImplType = IComplex_Specific<DRectCoords>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct ISpecificFor<AComplex, DRectCoords> {
|
||||||
|
using ImplType = IComplex_Specific<DRectCoords>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----- box with unique pointer -----
|
||||||
|
|
||||||
|
/** u for unique, b for box. Using lowercase for unobtrusiveness,
|
||||||
|
* so that in ub<MyType>, MyType is naturally emphasized
|
||||||
|
*
|
||||||
|
* @tparam ISpecific will be a specific interface,
|
||||||
|
* such as ISpecificFor<AComplex, DRectCoords>
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* OUniqueBox<AComplex, DRectCoords> z1 = ..;
|
||||||
|
* z1._xcoord(z1.data());
|
||||||
|
**/
|
||||||
|
template <typename AInterface, typename Data>
|
||||||
|
struct OUniqueBox : ISpecificFor<AInterface, Data>::typename ImplType {
|
||||||
|
Data * data() const { return data_.get(); }
|
||||||
|
|
||||||
|
up<Data> data_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Object>
|
||||||
|
struct RComplex : public Object {
|
||||||
|
double xcoord() const { return _xcoord(data()); }
|
||||||
|
double ycoord() const { return _ycoord(data()); }
|
||||||
|
double argument() const { return _argument(data()); }
|
||||||
|
double magnitude() const { return _magnitude(data()); }
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename AInterface, typename Object>
|
||||||
|
struct RoutingFor;
|
||||||
|
|
||||||
|
template <typename Object>
|
||||||
|
struct RoutingFor<AComplex, Object> {
|
||||||
|
using RoutingType = RComplex<Object>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename AInterface, typename Data>
|
||||||
|
struct ubox : public RoutingFor<AInterface, Data>::typename RoutingType { }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue