Perhaps you find this placement new use interesting. I use a class View
which holds an instance (not a ref or pointer!) of some class derived from Shape
. Each Shape
implementation can return a View
(by value, which we need, i.e. as a copy) with a copy of itself in it, but for all different types it's always the same View
! The View
class knows it holds a Shape
of some kind but is ignorant which subclass it really is.
This one-size-fits-all is achieved with placement new into a buffer held by a View
. Placement new creates an object of some type dynamically, but in a storage which is provided by the caller. Here the storage is the char array buf
in a local View
instance. The only requirement is that buf
must be large enough to hold the largest Shape.
This is fairly raw: alignment must be dealt with, and I'm not quite sure if and if so which restrictions apply to the classes. Multiple inheritance may be a problem (but now the code does not cast any longer).
I'm also unsure with respect to destructor issues and placement new. What if some Shape implementations need a destructor call to clean up after themselves?
The code is also unduly long.
But anyway:
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
/////////////////////////////////////////////////////////////////////
class Shape;
/// A class which can hold an instance of a class which derives from Shape.
/// It performs a placement new copy construction (i.e. the shape implementation
/// must be copy constructible).
class View
{
// The size of the buffer to construct shape implementations in.
// It must be at least the size of the largest shape implementation.
// Beware of polygons with more than a few dozen edges.
enum { SHAPE_BUFSIZE = 1000 }; // or whatever
public:
/// Place a copy of the argument in buf and return
/// a Shape* to it. Making this a template allows us to pull
/// this code from the implementing shape classes (where it
/// would be repeated for each shape) and centralize it here,
/// without losing the necessary concrete type information
/// for construction and size.
template <typename DerivedFromShape>
Shape *getViewShape(const DerivedFromShape &der)
{
// basic sanity check
static_assert(SHAPE_BUFSIZE >= sizeof(DerivedFromShape),
"buf too small");
// The interesting placement new to create a copy of the
// argument. The DerivedFromShape
// (i.e. here: Point or Point3D) is created in buf, without
// touching the heap or doing any other allocation. Cheap.
// If this assignment does not compile, DerivedFromShape
// does not derive from Shape.
thisShape = new(buf) DerivedFromShape(der);
return thisShape; // base class pointer
}
// Still provide default ctor.
View() = default;
// But no copy ctor (the default would be wrong or UB
// (can we effectively memcpy all shapes?)
// and we cannot easily copy the shape instance we
// hold without complete type information,
// which we cannot easily have here)
View(View &rhs) = delete;
// For manual manipulation if necessary
void setShapeAddr(Shape *shapeAddr) { thisShape = shapeAddr; }
Shape &shape() { return *thisShape; }
private:
// need to deal with alignment? skipped for now
char buf[SHAPE_BUFSIZE];
// This will hold the return value of new.
// (Not sure whether this is always guaranteed to be exactly buf.)
Shape *thisShape = 0;
};
/////////////////////////////////////////////////////////////////
/// The base class for Points, 3D points etc.
class Shape
{
public:
virtual Shape *produceViewShape(View &v) = 0;
// Actual Shape API
virtual ostream &print(ostream &) = 0;
virtual void moveTo(float x, float y) = 0;
// ... etc
};
/////////////////////////////////////////////////////////////////
// The simplest concrete shape.
class Point: public Shape
{
protected:
float x, y;
public:
Point(): x(0), y(0) {}
Shape *produceViewShape(View &v)
{
// calls correct template instantiation with Point
return v.getViewShape(*this);
}
// Shape API implementation
ostream &print(ostream &os) { return os << "Point(" << x << ", " << y << ")"; }
void moveTo(float xArg, float yArg) { x = xArg; y = yArg; }
// etc.
};
/////////////////////////////////////////////////////////////////
/// Another simple shape, deriving from a 2D Point.
class Point3D: public Point
{
protected:
float z;
public:
Point3D(): z(0) {}
Shape *produceViewShape(View &v)
{
// calls correct template instantiation with Point3D
return v.getViewShape(*this);
}
// Shape API implementation
ostream &print(ostream &os) { return os << "Point3D(" << x << ", " << y << ", " << z << ")"; }
void moveTo(float xArg, float yArg) { x = xArg; y = yArg; }
// etc.
};
///////////////////////////////////////////////////////////////////
/// show generic use of shape to generate a view
void moveShapeView(Shape &sh, float x, float y)
{
// on the stack
View v;
// A pointer to a "view" / a copy of the original sh.
// sh refers to an instance of some implementing derived class.
// The "view shape" copy has the same type and has
// been created by placement new.
// Its lifetime is that of the view object.
Shape *vsp = sh.produceViewShape(v);
// Manipulate this "view" and make it print itself
// so that we can check the actual type and that it
// is indeed a copy.
vsp->moveTo(x, y);
vsp->print(cout << "Moved shape view: ") << endl;
// view life ends here.
}
/// Helper function to initialize a vector of generic shape pointers
static vector<unique_ptr<Shape>> produceShapeVec()
{
vector<unique_ptr<Shape>> shapes;
shapes.emplace_back(make_unique<Point>());
shapes.emplace_back(make_unique<Point3D>());
return shapes;
}
/// A simple test: Does it compile, link and not crash?
/// Are view shapes actual copies?
int main()
{
// A vector of unique pointers to generic shapes.
// we don't know what actual types the pointed-to objects have.
auto shapes = produceShapeVec();
// Data to initialize some object state.
float x = 2, y = 3;
// Note: all access through Shape. If main was in a
// different TU it wouldn't need to know about Points etc.
for(auto &sh: shapes)
{
moveShapeView(*sh, x++, y++);
sh->print(cout << "But the original shape is still: ") << endl;
}
return 0;
}