简体   繁体   English

C ++协方差和参考

[英]C++ Covariance and references

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. 另一个很少被调用,因此最好根据需要创建对象以节省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. 我认为我可以使用协方差,因为Liskov替换原则会很高兴,但当然Obj不是Obj&的子类型,因此编译错误的结果。

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? 是否有一个更优雅的解决方案,而不是简单地挑选最差的?

One solution is to create a smart pointer class to manage BigObj* s: 一种解决方案是创建一个智能指针类来管理BigObj*

class BigObjPtr {
public:
    BigObjPtr(bool del, BigObj* ptr) : del(del), ptr(ptr) { }

    BigObj* operator->() {
        return ptr;
    }

    virtual ~BigObjPtr() {
        if (del) delete ptr;
    }

private:
    BigObj* ptr;
    bool del;
};

Then change your classes to return one of these, and set the del bool to whether you want the BigObjPtr to destroy its pointer when it goes out of scope: 然后更改您的类以返回其中一个,并将del bool设置为是否希望BigObjPtr在超出范围时销毁其指针:

class abc
{
public:
    virtual BigObjPtr obj() = 0;
};

class derived : public abc
{
public:
    ...
    BigObjPtr obj() { return BigObjPtr(false, &obj_); }

private:
    BigObj obj_;
};

class otherderived : public abc
{
public:
    ...
    BigObjPtr obj() { return BigObjPtr(true, new BigObj); }
};

You'd of course need to manage copying of BigObjPtr s, etc. but I leave that to you. 你当然需要管理BigObjPtr等的复制,但我把它留给你。

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: 注意:

expensive function() {
   expensive result;
   return result;
}
expensive x = function();

Can be optimized by the compiler into a single expensive object (it can avoid copying from result to the returned object and elide the copy from the returned object into x ). 可以通过编译器优化为单个expensive对象(它可以避免从result复制到返回的对象并将复制从返回的对象中删除到x )。

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. 在Liskov替换原则上,你没有在这里关注,一种类型是返回一个对象,另一种是返回一个对象的引用 ,这在很多方面都是完全不同的东西,所以即使你可以对这两个方面应用类似的操作返回类型,事实是有其他操作不相同,并且有不同的职责传递给调用者。

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. 为此,您需要明确用户代码对其获取的对象以及它对函数的期望。

Return a shared_ptr<BigObj> instead. 返回一个shared_ptr<BigObj> One class can keep it's own copy around, the other can create it on demand. 一个类可以保留它自己的副本,另一个可以按需创建它。

You have two options: 您有两种选择:

  1. As you noted, you can pick the least worst. 如你所知,你可以选择最差的。 That is, pick BigObj or BigObj& for both derived classes. 也就是说,为两个派生类选择BigObjBigObj&

  2. You could add new methods to the derived classes that have the appropriate return types. 您可以向具有适当返回类型的派生类添加新方法。 eg, BigObj& obj_by_ref() and BigObj obj_by_val() . 例如, BigObj& obj_by_ref()BigObj obj_by_val()

The reason you can't have it both ways is because you might have a pointer to abc directly. 你不能两种方式的原因是因为你可能直接指向abc It specifies BigObj& , so no matter which class provides the implementation, it had better return an BigObj& , because that's what the call site expects. 它指定BigObj& ,因此无论哪个类提供实现,它最好返回BigObj& ,因为这就是调用站点所期望的。 If a misbehaving subclass returned an BigObj 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. 我将BigObj重新设计为一个简单的委托(或容器)类,它实际上拥有指向昂贵对象的指针。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM