C++协方差和参考
假设我有一个带有纯虚拟的抽象基类,它返回一个昂贵的对象。由于它是一个昂贵的对象,我应该返回对它的引用。
但生活并没有那么简单,假设我有两个类派生自它:一个类具有经常调用的函数,因此在实例中存储副本并返回引用会更有效。另一个很少被调用,因此最好按需创建对象以节省 RAM。
我认为我可以只使用协方差,因为里氏替换原则会很高兴,但当然 Obj
不是 Obj&
的子类型,因此会导致编译错误。
class abc
{
public:
virtual BigObj& obj() = 0;
};
class derived : public abc
{
public:
...
virtual BigObj obj() { return obj_; }
private:
BigObj obj_;
};
结果是:
conflicting return type specified for ‘virtual BigObj derived::obj()’
是否有比简单地选择最不坏的更优雅的解决方案?
Lets say I have an abstract base class with a pure virtual that returns an expensive object. As it's an expensive object, I should return a reference to it.
But life's not that simple, let's say I have two classes derived from it: one has the function called often, so it is more efficient to store a copy in the instance and return a reference. The other is called rarely, so it is better to create the object on demand to save RAM.
I thought I could just use covariance because the Liskov substitution principle would be happy, but of course Obj
is not a subtype of Obj&
, so compile errors result.
class abc
{
public:
virtual BigObj& obj() = 0;
};
class derived : public abc
{
public:
...
virtual BigObj obj() { return obj_; }
private:
BigObj obj_;
};
Results in:
conflicting return type specified for ‘virtual BigObj derived::obj()’
Is there a more elegant solution to this than simply picking the least worst?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
一种解决方案是创建一个智能指针类来管理 BigObj*:
然后更改您的类以返回其中之一,并将
del
布尔值设置为您是否想要 < code>BigObjPtr 在超出范围时销毁其指针:您当然需要管理 BigObjPtr 等的复制,但我将其留给您。
One solution is to create a smart pointer class to manage
BigObj*
s:Then change your classes to return one of these, and set the
del
bool to whether you want theBigObjPtr
to destroy its pointer when it goes out of scope:You'd of course need to manage copying of
BigObjPtr
s, etc. but I leave that to you.您应该重新考虑您的假设,函数的接口应该根据语义来定义。因此,主要问题是函数的语义是什么?
如果您的函数创建了一个对象,无论要复制的对象有多大或多小,您都应该按值返回代码被调用的频率。另一方面,如果您正在做的是提供对已存在对象的访问,那么在两种情况下,您都应该返回对该对象的引用。
请注意:
可以由编译器优化为单个
expense
对象(它可以避免从result
复制到返回的对象,并消除从返回的对象复制到>x
)。关于里氏替换原则,你在这里没有遵循,一种类型是返回一个对象,另一种类型是返回一个对象的引用,这在很多方面都是完全不同的事情,所以即使你可以对两个返回类型应用类似操作,事实上,还有其他不同的操作,并且传递给调用者的职责也不同。
例如,如果您修改引用情况下的返回对象,则您将更改将来从函数返回的所有对象的值,而在值情况下,该对象拥有< /em> 由调用者调用,无论调用者对其副本执行什么操作,对该函数的下一次调用都将返回一个新创建的对象,而不会进行这些更改。
因此,思考函数的语义是什么,并使用它来定义在所有派生类中返回的内容。如果您不确定这段代码有多昂贵,您可以返回一个简化的用例,我们可以讨论如何提高应用程序的性能。为此,您需要明确用户代码如何处理它获取的对象,以及它对函数的期望。
You should rethink your assumptions, the interface of the functions should be defined in terms of what the semantics are. So the main question is what are the semantics of the function?
If your function creates an object, regardless of how big or small it is to copy you should return by value regardless of how often the code is called. If on the other hand, what you are doing is providing access to an already existing object, then you should return a reference to the object, in both cases.
Note that:
Can be optimized by the compiler into a single
expensive
object (it can avoid copying fromresult
to the returned object and elide the copy from the returned object intox
).On the Liskov substitution principle, you are not following here, one type is returning an object, and the other is returning a reference to an object, which are completely different things in many aspects, so even if you can apply similar operations to the two returned types, the fact is that there are other operations that are not the same, and there are different responsibilities that are passed to the caller.
For example, if you modify the returned object in the reference case, you are changing the value of all returned objects from the function in the future, while in the value case the object is owned by the caller and it does not matter what the caller does to its copy, the next call to the function will return a newly created object without those changes.
So again, think on what the semantics of your function are and use that to define what to return in all deriving classes. If you are not sure how expensive the piece of code is, you can come back with a simplified use case and we can discuss how to improve the performance of the application. For that you will need to be explicit in what user code does with the objects it gets, and what it expects from the function.
返回一个
shared_ptr
。一个类可以保留它自己的副本,另一个类可以根据需要创建它。Return a
shared_ptr<BigObj>
instead. One class can keep it's own copy around, the other can create it on demand.您有两个选择:
正如您所指出的,您可以选择最不最坏的。也就是说,为两个派生类选择
BigObj
或BigObj&
。您可以向具有适当返回类型的派生类添加新方法。例如,
BigObj& obj_by_ref()
和BigObj obj_by_val()
。不能同时使用这两种方法的原因是您可能有一个直接指向
abc
的指针。它指定了 BigObj&,因此无论哪个类提供实现,它最好返回一个BigObj&
,因为这是调用站点所期望的。如果行为不当的子类直接返回BigObj
,当编译器尝试将其用作引用时,将会导致混乱!You have two options:
As you noted, you can pick the least worst. That is, pick
BigObj
orBigObj&
for both derived classes.You could add new methods to the derived classes that have the appropriate return types. eg,
BigObj& obj_by_ref()
andBigObj obj_by_val()
.The reason you can't have it both ways is because you might have a pointer to
abc
directly. It specifiesBigObj&
, so no matter which class provides the implementation, it had better return anBigObj&
, because that's what the call site expects. If a misbehaving subclass returned anBigObj
directly, it would cause mayhem when the compiler tried to use it as a reference!在大多数情况下,通过引用返回是危险的,因为它可能导致难以跟踪内存问题,例如当父对象超出范围或被删除时。我会将 BigObj 重新设计为一个简单的委托(或容器)类,它实际上保存指向昂贵对象的指针。
Return by reference is dangerous in most cases as it can lead to hard to track memory issues, for example when the parent object goes out of scope or deleted. I would redesign the BigObj to be a simple delegate (or container) class that actually holds the pointer to expensive object.