1

I have a class Derived that inherits from an abstract class Abstract and that has a method that returns a "view" on the instance, i.e., another instance of the same class built from the current instance this :

class Abstract {
public : 
    Abstract(){};
    virtual void print() = 0;
};

class Derived : public Abstract {
public :
    Derived() : Abstract() {};
    void print() {std::cout << "This is Derived" << std::endl;}
    Derived view() {
        ... returns a temporary instance of the class Derived based
        ... on `this`
    } 
};

Now, let's say that I have another derived class Derived1 with the same mechanism :

class Derived1 : public Abstract {
public :
    Derived1() : Abstract() {};
    void print() {std::cout << "This is Derived1" << std::endl;}
    Derived1 view() {
        ... returns a temporary instance of the class Derived1 based
        ... on `this`
    } 
};

Now, I would like to get the view of an Abstract reference (i.e., either a Derived instance or a Derived1 instance) and get the result in another Abstract reference. I would love to be able to write something like that :

Abstract func_view(Abstract &a) {
    ... if a is an instance of class Derived 
    ... then : cast it to Derived and return a.view() which is
    ... an instance of Derived

    ... if a is an instance of class Derived1 
    ... then : cast it to Derived1 and return a.view() which is
    ... an instance of Derived1
}

But there is a big problem : the returned type is not correct. The following compiles but core dumps:

   void g(Abstract &b) {
       Abstract &&a = func_view(b);
       a.print(); // Here it core dumps saying that `print` is purely virtual.
   }

   int main (...) {
       Derived d;
       g(d);
   }

Is there a solution to that problem ? The problem seems really in the returned type of the function func_view. If, instead, the method view for both classes Derivedand Derived1 where an instance of Derived, then with the declaration

 Derived func_view(Abstract &b) {
     .....
 }

The whole thing would work perfectly !

Any idea ?

Manu
  • 11
  • 3
  • 3
    You could dynamically allocate your view object and return a `unique_ptr`. The basic problem you have is that you're trying to pass an abstract class by value, which doesn't work. I don't immediately understand why the function `func_view` even compiles. – Steve Jessop Jan 04 '16 at 22:43
  • 1
    See here: http://stackoverflow.com/questions/274626/what-is-object-slicing – pokey909 Jan 04 '16 at 22:47
  • If you google "clone" implementations for C++ you'll find the function types and logic you need, except that you won't copy the object data verbatim - instead providing your alternative "view". – user433534 Jan 04 '16 at 22:56
  • For (local?) read acces only you may be able to somehow use const references to `Abstract` referencing the respective instance of a derived class. Gets you polymorphism without dynamic allocation. Probably together with LogicStuff's suggestion to declare an abstract `view()` in `Abstract`. – Peter - Reinstate Monica Jan 04 '16 at 23:06
  • @SteveJessop : I would like to avoid pointers and dynamic allocation – Manu Jan 04 '16 at 23:16
  • @PeterA.Schneider : Yes You are right. That would work. But then the print() method will have to be declared as constant and all the methods that I would like to call. This is a weird restriction : why declaring the reference constant helps ? I don't understand the logic. Why is the comelier bothered by the fact that the content of the instance could be changed ? – Manu Jan 04 '16 at 23:18
  • Because it's a temporary. – Peter - Reinstate Monica Jan 04 '16 at 23:40
  • @PeterA.Schneider : Sorry I don't get it : Functions in C++ are full of temporary object that are not constants !? Am I missing something ? – Manu Jan 04 '16 at 23:58
  • The return value of a function is a temporary (you can assign it to something but it ceases to exist at the end of the expression, unless bound to a const reference). You could initialize a reference to `Abstract` with a temporary of any derived type, which would give you the desired uniformity. – Peter - Reinstate Monica Jan 05 '16 at 06:28
  • @PeterA.Schneider : It looks like you are suggesting a solution, could you be more explicit. I don't really understand the code you would write. Thank you. – Manu Jan 05 '16 at 08:36
  • I fiddled a bit and I cannot get it to work., sorry. – Peter - Reinstate Monica Jan 05 '16 at 11:42
  • What I come up with is to define a view class, implementing `Abstract`, as an aggregate of all deriveds (which is hard to maintain, defies all kinds of OO principles and may become too large anyway), and return that by value, with an internal flag indicating which of the various derived members is actually used.-- More reasonable seems a placement new solution with a buffer provided by the caller. Each derived would implement creating a copy of itself into the buffer which can live as long as desired. An `Abstract` reference to that is always possible, without knowing which derived is in it. – Peter - Reinstate Monica Jan 05 '16 at 14:52
  • @PeterA.Schneider : Thank you for trying. Yes I finally tried on my side .... it is rather complex I must say. – Manu Jan 05 '16 at 15:04
  • @Manu: Not really related to your question, but if you use inheritance and virtual member functions, you should ALWAYS add a virtual destructor to the base class. You could get undefined behaviour otherwise. – smerlin Jan 05 '16 at 16:42

2 Answers2

0

You cannot return an abstract type from a function, put you can return a pointer to an abstract type. You can't really do a lot with instances of abstract classes. You can't copy them, or move them around. And, since returning an abstract class is an implicit copy operation (or is equivalent to a copy/move operation), you can't do that either. About the only thing you can do is invoke one of their methods.

At about this point, one will often realize that once a class hierarchy reaches a certain complexity, it requires a paradigm shift to using reference-counted objects, better known as std::shared_ptr. Instead of tossing class instances, back and forth, it becomes necessary to start tossing references to reference-counted objects, and rely on your underlying reference-counted framework to destroy the object, once the last reference to the instance goes out of scope.

So, your func_view() method will need to return a std::shared_ptr to an abstract class, and once you've finished sifting through the existing code base, converting every usage of the abstract class to a std::shared_ptr to the abstract class, you'll be all set.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Yes, I know about the shared_ptr stuff of course. But as I sait above, I want to avoid dynamic allocation. Moreover I do not agree with you on the abstract class problem. I am managing only references to this class. This should be possible. Actually, I would have the exact same problem if the `Abstract` class was not abstract (implementing the `print` method). – Manu Jan 04 '16 at 23:24
  • If you want to avoid dynamic allocation, the only way to do it in this context is to use callback semantics. The func_view() function must be an abstract class method, implemented in the derived class, that takes a reference to a callback object, or a function pointer as a parameter, then invokes it passing "\*this" as the parameter, with the callback receiving a reference to the abstract superclass, as its parameter. – Sam Varshavchik Jan 04 '16 at 23:32
  • @Varshavchik : sorry I don't get it. The whole problem is to extend the lifetime of an temporary instance referred to as `Abstract &`. What type would your method `fund_view` return ? Same question for the pointer function ? – Manu Jan 04 '16 at 23:55
  • @Manu: you can't extend the lifetime of a "`Derived` or `Derived1`, I don't know which" by binding it to a reference because the code that calls `func_view()` doesn't know the most-derived type of the object. So it can't put it on its stack. You're trying to make the type of the object `Abstract`, which is no good because that's an abstract type (and even if it did result in an object of type `Abstract`, that would slice the original object of type `Derived`, so it wouldn't do what you want). – Steve Jessop Jan 05 '16 at 00:02
  • @SteveJessop : When I write `fund_view(Abstract &a)` the variable `a` is a reference to an `Abstract` and it knows the exact type (in the same way as a pointer `Abstract *`). I can create easily a temporary variable `Abstract & b` in `func_view` that holds a reference to the created view (i.e., having access to the true type and the corresponding virtual methods). Now why could not I extend the lifetime of this temporary variable ? – Manu Jan 05 '16 at 00:08
  • @Manu: for the reason I stated: the mechanism which does this needs to know the complete type of the object at compile-time, in order to make space for it on the stack (and to call the right destructor, although I didn't mention that earlier). In any case the issue isn't really the lifetime of the variable (of reference type), it's the lifetime of the object that the variable refers to. – Steve Jessop Jan 05 '16 at 00:13
  • @SteveJessop : Sorry I answered to quickly. I think I understand your point. As you say the code calling `func_view` should be able to understand the size of the object to move on the stack. So you are basically saying there is absolutely no solution ? – Manu Jan 05 '16 at 00:17
  • Not that satisfies both of your constraints: must be a temporary object *and* must be polymorphic with complete type unknown until runtime. So sure, you can use references to refer to runtime-polymorphic objects, and make virtual calls on them. But the lifetime-extension mechanism applies to temporary objects, and these necessarily have known complete types. As I said earlier, I think your compiler should have rejected your code as soon as it sees `Abstract` as a return type. I'm not sure why it didn't but I think that's given you false hopes... – Steve Jessop Jan 05 '16 at 00:20
  • @SteveJessop : but it is allowed to return `Abstract &`. For instance if I have two static variables which are instances of `Derived` and `Derived1` I can return conditionally any of them in `func_view` declaring `Abstract & func_view(..)`. So I don't understand why it would not be technically possible to do the same thing on a temporary object and ask (explicitly) for lifetime extension at the same time ? Is it because the static variables would not live on the stack ? – Manu Jan 05 '16 at 00:34
  • @Manu: if you return `Abstract&`, and the reference you return refers to an object on the stack of `func_view()` (including, but not limited to, a temporary object in `func_view()`), then the object is already destroyed and the reference is already dangling by the time `func_view()` returns. There is no temporary object in the calling function, only an already-dead-and-gone object in the callee. Returning a reference to a static object doesn't have this problem (so yes, it's because the static objects aren't on the stack of the callee). – Steve Jessop Jan 05 '16 at 00:39
0

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;
 }      
Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62