向下转发返回的临时对象C ++

时间:2016-01-04 22:38:26

标签: c++ inheritance

我有一个类Derived,它继承自抽象类Abstract,并且有一个方法可以返回一个"视图"在实例上,即从当前实例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`
    } 
};

现在,让我们说我有另一个具有相同机制的派生类Derived1

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`
    } 
};

现在,我希望获得Abstract引用(即Derived实例或Derived1实例)的视图,并将结果输入另一个Abstract参考。我希望能够写出类似的东西:

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
}

但是有一个大问题:返回的类型不正确。以下编译但核心转储:

   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);
   }

这个问题有解决方案吗?问题似乎确实在函数func_view的返回类型中。相反,如果viewDerived类的方法Derived1,其中Derived的实例,则使用声明

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

整件事情会完美无缺!

有什么想法吗?

2 个答案:

答案 0 :(得分:0)

您无法从函数返回抽象类型,您可以返回指向抽象类型的指针。你可以用抽象类的实例做很多事情。你无法复制它们,也不能移动它们。并且,由于返回抽象类是隐式复制操作(或等同于复制/移动操作),因此您也不能这样做。关于你唯一能做的就是调用他们的一种方法。

在这一点上,人们经常会意识到,一旦类层次结构达到一定的复杂性,就需要使用引用计数对象的范式转换,更好地称为std::shared_ptr。而不是来回地抛出类实例,有必要开始抛弃对引用计数对象的引用,并且一旦对实例的最后引用超出范围,依赖于底层引用计数框架来销毁对象。 / p>

因此,您的func_view()方法需要将std::shared_ptr返回给抽象类,并且在完成对现有代码库的筛选后,转换抽象的每个用法

答案 1 :(得分:0)

也许您会发现此展示位置的新用途很有趣。我使用了一个类View,它保存了从Shape派生的某个类的实例(不是引用或指针!)。每个Shape实现都可以返回View(按值,我们需要,即作为副本),其中包含自身的副本,但对于所有不同的类型,它始终是相同的ViewView类知道它拥有某种类型的Shape,但它实际上是无知的子类。

通过将{new}放入由View持有的缓冲区中,可以实现这种“一刀切”。 Placement new会动态创建某种类型的对象,但是在调用者提供的存储中。这里的存储是本地buf实例中的char数组View。唯一的要求是buf必须足够大以容纳最大的Shape。

这是相当原始的:必须处理对齐,我不确定是否以及是否适用于类的限制。多重继承可能是个问题(但现在代码不再存在了)。

我也不确定析构函数问题和放置新问题。如果某些Shape实现需要析构函数调用来自行清理,该怎么办?

代码也过长。

但无论如何:

#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;
 }