【问题标题】:Copying objects with only move semantics仅使用移动语义复制对象
【发布时间】:2012-11-28 09:12:46
【问题描述】:

首先,让我带你们进入我的思想大道(简单地说,我只是在想象这些事情

假设,我正在使用使用移动语义(右值引用)的第三方库类。这是它的定义:

class VeryHeavyObject {
    VeryHeavyObject(const VeryHeavyObject& obj); // copy constructor
    VeryHeavyObject& operator= (const VeryHeavyObject& obj); // copy operator

public:
    VeryHeavyObject(); // constructor

    VeryHeavyObject(VeryHeavyObject&& obj); // move constructor
    VeryHeavyObject& operator= (VeryHeavyObject&& obj); // move operator

    // ....
};

显然,作者真的很担心复制VeryHeavyObject 的成本,并决定强制移动所有内容(更明显的是,他不知道如何设计具有移动语义的类)。但是,在我的代码中,我需要有一个VeryHeavyObject副本

嗯,核心问题是: 如何仅使用移动构造函数和移动运算符来复制对象?

P.S.:我已经尝试过,但我无法真正联系到图书馆的作者(我认为他正在度假)。

【问题讨论】:

  • VeryHeavyObject有完整的接口吗?可能有.clone() 方法。
  • @KennyTM 可能作者只是不知道如何设计类,忘记了克隆方法。 (请明白我的意思)
  • 是因为对象很重,还是因为它真的不应该被复制(例如它代表一个独特的资源)?
  • 为什么首先需要复制它?可能没有必要。例如使用 emplace_back 而不是 push_back 用于 stl 容器...
  • 注意:在 C++11 中,您应该使用= delete 来删除方法,而不是仅仅将它们设为private(如果仍在使用它们会引发链接错误)。但是它没有实际意义,因为 §12.8/7 精确如果类定义声明了移动构造函数或移动赋值运算符,则隐式声明的复制构造函数被定义为已删除;,因此您正在做额外的工作妨碍编译器的诊断;)

标签: c++ c++11 rvalue-reference


【解决方案1】:

你不能。

但是,如果您有足够的权限访问其内部(getter 等),那么您可以自己构造一个克隆

【讨论】:

    【解决方案2】:

    一个定义良好的接口,我们将假设是这种情况,某些方法可能不可用,因为作者出于性能原因不鼓励某些用途。一个著名的例子是std::list,它不包含[] 运算符,因为与其他容器中的O(1) 相比,它具有O(n) 的复杂性,例如std::vector

    在这种情况下,图书馆的作者不鼓励使用副本,因为正如您在问题中所说的那样,它的成本非常高。但这并不意味着它是不可能的。如果您确实需要这样做,您可以编写自己的Clone() 函数,该函数根据需要从原始VeryHeavyObject 中获取数据,用这些数据构造一个新数据并使用std::move 返回它。由于我们没有VeryHeavyObject 的接口,我们无法尝试这样做,但我相信你可以。

    【讨论】:

    • 我想这样做而不修改类的界面。
    • @MarkGarcia:你看错了,Gorpik 从来没有提到修改类接口。
    • @MarkGarcia Matthieu 是对的,我不是要求您修改类接口,而是添加一个免费功能来进行克隆。这或多或少是他本人在接受的答案中所建议的,所以这对我来说很好。
    【解决方案3】:

    这可能是不可能的。

    该类已声明这些副本是私有的,但我们看不到这些函数是否曾经定义。您似乎认为该类有一个复制操作,它对您隐藏起来以阻止您做一些缓慢的事情,但情况可能并非如此。有些对象根本无法复制。例如,考虑流。

    您不会期望在 C++11 中有私有声明但未定义的函数,但没有法律禁止它。无论如何,即使有一个实现的私有复制功能,它也可能是私有的(也许它只能在某些受控情况下使用:类内部知道如何安全地使用它而你不知道)。所以如果没有公共副本,那么就这个类的 API 而言,它是不能被复制的。

    也许这个类有足够的公共访问器,你可以查询它以获得你需要的状态,并构造一个与之匹配的新对象。如果是这样,那么您可以合理地向该课程的作者抱怨它应该是可公开复制的。如果不是,那么它可能具有无法复制的状态。

    任何提供对某事物(流、驱动程序、锁)的唯一访问权的事物都有不可复制的理由,因为原始和副本不能同时提供对同一事物的唯一访问权。诚然dup 意味着即使文件描述符在物理上也不提供对某些东西的唯一访问,更不用说包装它们的流了。但是流的状态涉及尚未写入的缓冲数据,这意味着复制它们会引入该类旨在保护您免受影响的复杂性。所以从逻辑上讲,您通常使用流,就好像它是访问某些东西的唯一方式。

    如果实现了复制赋值运算符,那么您可能能够破解a way to call it even though it's private。但是,这不适用于复制构造函数,您不能将指针指向构造函数。作为一个残酷的黑客,您可以 #define private public 在包含其标题之前:这是未定义的行为,但它可能适用于您正在使用的实现。分叉第三方源会更好。

    【讨论】:

    • 所以没有(至少是可移植的)复制这些对象的方法。
    • @MarkGarcia:没有复制任何此类对象的通用方法,不。您必须查看特定的类,找出它不能公开复制的原因,然后决定您或作者是否可以对此做任何事情。
    【解决方案4】:

    一般来说,不修改类是不可能的,因为可能存在您无法访问的私有数据。如果浅拷贝就足够了,那可能是可能的,因为那时你should be able to do it with a memccpy。 (注意,如果类没有任何虚成员或指针,浅拷贝和深拷贝是一样的)。

    【讨论】:

    • 我认为,一旦原件被破坏,可以有效移动的对象几乎不可能按位复制而不会对副本造成严重破坏。
    猜你喜欢
    • 1970-01-01
    • 2013-10-17
    • 2015-02-09
    • 2011-04-21
    • 1970-01-01
    • 1970-01-01
    • 2018-03-27
    • 2020-11-11
    • 1970-01-01
    相关资源
    最近更新 更多