我有一个类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
的返回类型中。相反,如果view
和Derived
类的方法Derived1
,其中Derived
的实例,则使用声明
Derived func_view(Abstract &b) {
.....
}
整件事情会完美无缺!
有什么想法吗?
答案 0 :(得分:0)
您无法从函数返回抽象类型,您可以返回指向抽象类型的指针。你可以用抽象类的实例做很多事情。你无法复制它们,也不能移动它们。并且,由于返回抽象类是隐式复制操作(或等同于复制/移动操作),因此您也不能这样做。关于你唯一能做的就是调用他们的一种方法。
在这一点上,人们经常会意识到,一旦类层次结构达到一定的复杂性,就需要使用引用计数对象的范式转换,更好地称为std::shared_ptr
。而不是来回地抛出类实例,有必要开始抛弃对引用计数对象的引用,并且一旦对实例的最后引用超出范围,依赖于底层引用计数框架来销毁对象。 / p>
因此,您的func_view
()方法需要将std::shared_ptr
返回给抽象类,并且在完成对现有代码库的筛选后,转换抽象的每个用法1}}到抽象类的类,您将全部设置。
答案 1 :(得分:0)
也许您会发现此展示位置的新用途很有趣。我使用了一个类View
,它保存了从Shape
派生的某个类的实例(不是引用或指针!)。每个Shape
实现都可以返回View
(按值,我们需要,即作为副本),其中包含自身的副本,但对于所有不同的类型,它始终是相同的View
! View
类知道它拥有某种类型的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;
}