【问题标题】:Move value from local stack to heap? (C++)将值从本地堆栈移动到堆? (C++)
【发布时间】:2011-11-26 03:26:47
【问题描述】:

如何从方法调用中获取对象即值结果并将其放在堆上?

例如:

Qt QImage::scaledToWidth method 返回QImage 对象的副本。

我现在正在做:

QImage *new_img_on_heap = new QImage(old_imgage_on_heap->scaledToWidth(2000));

这是唯一的方法吗?当我在堆栈上已经有一个完美的好对象时,似乎正在经历制作第三个全新对象的麻烦。

我想把它放在堆上的原因是因为真正的QImage 很大,我希望它比当前方法的生命周期更长。我打算在我的类的一个字段中填充一个指向它的指针。

我知道QImage 有某种隐式数据共享,但我并不清楚它是如何工作的。另外,如果我需要使用设计不如 Qt 的对象,我想知道一个通​​用的解决方案。

【问题讨论】:

  • 我认为复制到一个新的堆分配对象是正确的方法(无论如何需要进行堆内存分配)。我只是想知道 C++ move 将如何在幕后处理这个问题。
  • @Clodéric:如果类型是可移动的,那么它将调用移动构造函数而不是复制构造函数(参数是临时的)。我会假设 QImage 对象在库的 C++0x 兼容版本中是 movable (图像缓冲区很可能保存在动态内存中)
  • @DavidRodríguez-dribeas 我知道,我只是想知道它是如何在堆和堆栈之间移动对象的底层工作原理(如果可以不复制的话——我怀疑)。
  • @Clodéric:它不会移动对象,而是对象管理的资源。假设 QImage 包含一个指向字节数组的指针,那么移动构造函数可以交换源对象和目标对象中的指针。仍然会有 2 个对象,但源的内容将被 移动 到第二个对象。 same approach 可以在 C++03 中手动处理(在某些情况下),方法是创建一个空对象并使用手工定制的 swap 函数。

标签: c++ heap-memory stack-memory


【解决方案1】:

对象由其地址标识。如果你想在另一个 地址,您必须构建一个新地址;你不能移动物体。 (甚至 对于 C++11,新的“移动”语义实际上并不移动 目的;它们提供了一种优化的方式来转移其价值,如果你知道的话 你不需要移动它的值。)

【讨论】:

  • +1:非常重要注意对象和值的区别,move-constructormove-assignmentmove 对象,只能移动内容(并非总是如此)
【解决方案2】:

首先,在大多数情况下,您应该更喜欢堆栈而不是堆。

其次,当您在堆上分配对象时,您应该将原始指针包装在堆栈上的某个托管对象中。如果没有别的原因,请出于异常安全考虑。

第三,Qt 对象通常是do exactly that,即写new QImagenew QMap<int> 等没有优势。

不过,将非堆对象移动到堆上的正确方法是说

new ObjectType (old_instance)

如果您使用临时对象执行此操作,例如

new ObjectType (create_object ())

那么它可能会elide the extra copy(我的链接坏了——cached

【讨论】:

  • 栈和堆(或者更准确地说是自动存储还是动态存储)的决定应该与对象的生命周期相关,而不是对象的大小。如果 QImage 要超过当前上下文,那么您不能在当前范围内拥有自动存储持续时间。
  • copy-elision 开始,它不会在那种特定情况下发生。特别是如果目标对象不是具有自动存储持续时间的对象,则可能无法删除副本。您可以通过复制省略 herehere 阅读窗帘下的内容。如果编译器可以为对象和临时对象使用相同的内存,则编译器只能删除副本,其位置由调用约定确定。
  • 我完全同意对象生命周期更为重要,但足够大的对象必须放在堆上,否则会破坏堆栈。我也同意省略取决于调用约定。但是,我或 OP 的示例中没有任何内容可以抑制它。
  • 足够大对于没有经验的程序员来说实际上是很难识别的。包含 10 个元素的std::vector<int> 是否足够大? 100.000.000 个元素呢?还是向量中的元素数量根本不重要?来自 18M 像素相机的 QImage 怎么样,是否足够大 必须使用动态内存?存储图像的大小真的很重要吗?
  • @spraff:我已经回到基础并阅读了调用约定,这是我不久前应该做的事情。在 32 位和 64 位中按值返回的调用约定,Microsoft 和 GCC 不使用对象的固定位置(正如我错误地认为的那样),而是传递一个指向要返回的对象位置的指针(除非它适合寄存器),这意味着对象可以位于任何地方。我错误地认为返回的对象(对于大对象)必须放在堆栈中。 +1
【解决方案3】:

由于方法

QImage QImage::scaledToWidth (int width, Qt::TransformationMode mode = Qt::FastTransformation) const

不返回 QImage 地址(即 QImage& ....),而不是我认为你不走运。你被副本困住了。除非您想重新编译 QT 库以更改该函数签名以使其不返回副本。

把那个对象放在堆栈上有什么不好?

【讨论】:

    【解决方案4】:

    您可以尝试以下技巧。想法是将您的价值放在一些结构中并在堆中构建结构。在这种情况下,复制省略有效。不执行复制或移动。不幸的是,它不适用于 VS2017。但它适用于 G++ 8+,可能适用于 VS2019。

    #include <memory>
    
    struct C{
    
        C() = default;
        C( C&& ) = delete;
        C( C const& ) = delete;
    
        C& operator=( C&& ) = delete;
        C& operator=( C const& ) = delete;
    };
    
    C foo() {
        return C {};
    }
    
    struct X {
        C c;
    
        X() 
            : c (foo()) // here is how trick works
        {
        }
    };
    
    int main() {
        auto x = std::make_unique<X>();
    }
    

    【讨论】:

      【解决方案5】:

      这个问题的前提是不正确的,因为它假设 QImage 的图像数据与 QImage 对象本身具有相同的生命周期。

      我想把它放在堆上的原因是因为真正的 QImage 很大,我希望它比当前方法的生命周期更长。我打算在我的类的一个字段中填充一个指向它的指针。

      这个陈述不正确,因为它对于像 QImage 这样的东西来说是一件非常糟糕的事情,并且很快就会因为弹出“堆栈”限制而失败。这也会使分配变得非常缓慢!

      相反,QImage 就像许多处理大型和/或外部数据的 C++ 对象一样,实际上是一个围绕不同生命周期的内部管理内存的包装器。这可以在the QImage source code 中看到; QImage 包含 QImageData,而 QImageData malloc 的内部缓冲区独立于原始 QImage 的生命周期。

      由于大部分数据都在 QImage 的“外部”,因此几乎没有理由更喜欢 new QImage 而不是使用 QImage 和自动生命周期,以便(错误地感知)图像数据的存储持续时间; QImage 实现了健全的复制和移动*,因此所有底层图像数据(以及这些底层分配)都是共享的。

      *在QImage's copy ctor 中,QImageData 的“引用计数”被短暂扩展,直到前一个对象的销毁,而在移动中,QImageData 的生命周期可以直接转移(在评论中,从该源中丢失?)。无论如何,这两种情况都不会真正复制底层图像数据,这会很昂贵。

      所以,回答这个问题:new X(non_ptr_X),让正确编写的构造函数/设计做他们的事情,这是一个实现细节;并希望是令人满意的性能特征之一。 YMMV。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-30
        • 2018-08-29
        • 1970-01-01
        • 1970-01-01
        • 2012-08-25
        相关资源
        最近更新 更多