C++协方差和参考

发布于 2024-12-01 23:32:46 字数 588 浏览 0 评论 0原文

假设我有一个带有纯虚拟的抽象基类,它返回一个昂贵的对象。由于它是一个昂贵的对象,我应该返回对它的引用。

但生活并没有那么简单,假设我有两个类派生自它:一个类具有经常调用的函数,因此在实例中存储副本并返回引用会更有效。另一个很少被调用,因此最好按需创建对象以节省 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

巾帼英雄 2024-12-08 23:32:46

一种解决方案是创建一个智能指针类来管理 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;
};

然后更改您的类以返回其中之一,并将 del 布尔值设置为您是否想要 < code>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); }
};

您当然需要管理 BigObjPtr 等的复制,但我将其留给您。

One solution is to create a smart pointer class to manage BigObj*s:

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:

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 BigObjPtrs, etc. but I leave that to you.

少女情怀诗 2024-12-08 23:32:46

您应该重新考虑您的假设,函数的接口应该根据语义来定义。因此,主要问题是函数的语义是什么?

如果您的函数创建了一个对象,无论要复制的对象有多大或多小,您都应该按值返回代码被调用的频率。另一方面,如果您正在做的是提供对已存在对象的访问,那么在两种情况下,您都应该返回对该对象的引用。

请注意:

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

可以由编译器优化为单个 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:

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).

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.

孤星 2024-12-08 23:32:46

返回一个 shared_ptr 。一个类可以保留它自己的副本,另一个类可以根据需要创建它。

Return a shared_ptr<BigObj> instead. One class can keep it's own copy around, the other can create it on demand.

神经大条 2024-12-08 23:32:46

您有两个选择:

  1. 正如您所指出的,您可以选择最不最坏的。也就是说,为两个派生类选择 BigObjBigObj&

  2. 您可以向具有适当返回类型的派生类添加新方法。例如,BigObj& obj_by_ref()BigObj obj_by_val()

不能同时使用这两种方法的原因是您可能有一个直接指向 abc 的指针。它指定了 BigObj&,因此无论哪个类提供实现,它最好返回一个 BigObj&,因为这是调用站点所期望的。如果行为不当的子类直接返回 BigObj,当编译器尝试将其用作引用时,将会导致混乱!

You have two options:

  1. As you noted, you can pick the least worst. That is, pick BigObj or BigObj& for both derived classes.

  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().

The reason you can't have it both ways is because you might have a pointer to abc directly. 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. If a misbehaving subclass returned an BigObj directly, it would cause mayhem when the compiler tried to use it as a reference!

金橙橙 2024-12-08 23:32:46

在大多数情况下,通过引用返回是危险的,因为它可能导致难以跟踪内存问题,例如当父对象超出范围或被删除时。我会将 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文