【问题标题】:How to make a class movable by other classes?如何让一个类被其他类移动?
【发布时间】:2020-11-23 07:28:33
【问题描述】:

假设我有一个 RAII 课程,例如 vector

class vector{
   double* data_;
   int size_;
public:
   explicit vector(int size = 0) : data_{new double[size]}, size_{size}{}
   vector(vector const& other) : data_{new double[other.size_]}, size_{other.size_}{}
   int size() const{return size_;}
   double const* data() const{return data_;}
   double* data(){return data_;} // optional but common
   ~vector(){if(size_) delete[] double;}
}

如果我想让类可移动,我可以添加一个移动构造函数。

   vector(vector&& other) : size_{other.size_}, data_{other.data_}{other.size_ = 0;}

到目前为止,模数错别字和迂腐的 cmets,仅此而已。

但是,我想让与我的vector 无关的其他 类移动该类。 我知道这基本上需要类似于移动构造函数的代码,但在一个独立的类中。

class SuperVector{
   double* data_begin_;
   double* data_end_; // I don't use size to show having two independent implementations
   std::string super = "super";
   SuperVector(vector&& v) ... {...} // what here? what needs to change in `vector`?
};

我几乎可以肯定,无论SuperVector 代码如何,vector 都需要以某种方式进行更改以允许这样做。

问题是是否有一个常用的协议来允许这些不相关类的可移动性。或者这是尚未考虑的事情。 (我想偶尔人们会想从std::vector 转移到一个不相关的类)。


前期工作:

我能想到的解决方案是:

  1. SuperVector成为vector朋友,然后简单地实现“进入”的移动

       SuperVector(vector&& v) : data_begin_{v.data()}, data_end_{v.data() + v.size()}{v.size_ = 0;}
    

    这个问题是友谊增加了很多耦合,它不是一个通用的解决方案。

  2. 提供对向量内部表示的更多访问权限(特别是使其可分配)。

       int& vector::size(){return size_;}
    
       SuperVector(vector&& v) : data_begin_{v.data()}, data_end_{v.data() + v.size()}{v.size() = 0;}
    

    这真的很糟糕,因为任何人都可以更改size、破坏不变量等。

    现在是更复杂和重要的选项。

  3. 类似 2) 但添加了特殊功能:

    class vector{...
       [[nodiscard]] // false sense of security
       double* moved_data()&&{ // for lack of a better name (simply data()&&?)
          size_ = 0;
          return data_;
       }
    ...}
    

    并将其用作

       SuperVector(vector&& v){ // or some variation of this
         data_end_ = v.data() + v.size()}; 
         data_begin_ = std::move(v).moved_data();
       }
    

    这样做的缺点是vector需要修改,这是意料之中的。 但是,任何人都可以调用moved_data 并使vector 无效,这似乎很危险。 此外,这取决于[[nodiscard]],并且可能会从不相关的类中产生内存泄漏。 最糟糕的vector 似乎“过早地”处于从状态移动的状态,远在对象真正从(客户端)其他类中移动之前。

    最后,

  4. 也许我缺少的是某种新的智能指针,它可以延迟移动到以后的时间。 我在这里称它为move_ptr

    template<class CustomMover>
    class move_ptr{ // or transfer_ptr
       double* ptr_;
       CustomMover mover_;
    public:
       move_ptr(double* ptr, CustomMover mover) : ptr_{ptr}, mover_{std::move(mover)}{}
       // probably movable too.
       [[nodiscard]] operator double*(){ // protect against twice move
          if(ptr_) mover_();
          auto ret = ptr_; ptr_ = nullptr; return ret;
       }
    //   ~move_ptr(){} // if nobody took care, that is ok, nothing happens.
    }
    
    class vector{...
       auto moved_data()&&{ // for lack of a better name (or simply data()&&?)
          auto custom_mover = [&size_]{size_ = 0;};
          return move_ptr<decltype(custom_mover)>{data_, custom_mover_};
       }
    ...}
    

    (其他可能的名称是move()&amp;mdata()&amp;mdata()&amp;&amp;data()&amp;&amp;。)

    请注意,移动的副作用是由智能指针携带的。

    并将其用作

       SuperVector(vector&& v){ // or some variation of this
         data_end_ = v.data() + v.size()}; 
         data_begin_ = std::move(v).moved_data(); // this will set size to zero
       }
    

    我确信这个简单的代码没有考虑到一些极端情况,但我希望主要思想是明确的。

    欢迎修复。

    这个智能指针有意义吗,还是已经在某个地方实现了类似“移动管理器指针”的东西?

    我看到这类似于std::unique_ptr,除了移动是由复制的智能指针实现的,而且自定义操作仅在赋值时发生(到常规指针)。

    也许std::unique_ptr 可以达到相同的效果,但我不知道如何。

这是这个更抽象问题的具体应用:Exact correspondence between r-value references and pointers?

【问题讨论】:

  • 也许你想多了?你真的需要两个不同的向量类,唯一的区别是存储大小和存储指向缓冲区末尾的指针吗?
  • @HolyBlackCat,不,这只是更复杂数据结构的简化示例。即使表示相同 data_ + size_ 我也会遇到同样的问题。您建议的非过度思考的解决方案基本上是 SuperVector 包含一个 vector 并完成它。
  • 如果我必须这样做,我会选择友谊。这是不暴露脏不安全函数的唯一方法。此外,您为什么关心“耦合”?如果您必须将向量移动到 SuperVector,则它们 耦合/相关。如果不建立它们之间的关系,您就无法从一个移动到另一个。这对我来说没有意义。
  • vector 类需要公开一种方法来释放其内部管理的资源(就像std::unique_ptrrelease 所做的那样),如果它没有这种可能性,那么您要么需要存储@ 987654359@ 内SuperVector 或复制数据。所以如果SuperVector使用vector作为内部存储的选项,并且只是为其提供一个自定义接口,那么它是可能的。
  • 关于[…] the question is what a release-like should return given the implementation of vector.[…][…]if there is a protocol commonly used to allow these moveability from an unrelated class. Or this is something that it is not contemplated yet.[…],您可以考虑基于swap 的模板,但最大的缺陷是,您通常不想这样做。如果您公开内部存储详细信息,您会阻止自己在以后对其进行更改,并且您会被设计决策卡住,这就是您不会经常看到此类构造的原因。

标签: c++ move smart-pointers unique-ptr move-semantics


【解决方案1】:

您已经了解了所有可能性,您的问题的直接答案是不存在通用协议。

这个答案回顾了 std::lib 中允许将所有权转移到不相关类型的几个地方。

unique_ptr

auto p = up.release();

unique_ptrup 已放弃其资源的所有权,现在由客户端负责。

unique_lock

auto m = ul.release();

unique_lockul 已放弃对其互斥锁的锁定状态的所有权,现在由客户端负责。

关联容器和无序容器

extract 成员函数查找单个元素(通过键或迭代器)并在名为container::node_type 的类似智能指针的对象中释放单个节点的所有权。这个“智能指针”有一个分配器的副本,因此它知道如何自毁。它还为客户端提供了对包含元素的非常量访问。当元素的关键部分归容器所有时,只有 const 访问可用。

【讨论】:

  • 太好了,我的问题刚刚获得 HH 徽章(霍华德在不到 24 小时内回答了一个或多个问题)。
  • 酷,谢谢!我错过了与节点句柄的类比。 en.cppreference.com/w/cpp/container/node_handle。我要调查一下。
【解决方案2】:

基本上不可能有一个通用的解决方案。从根本上移出一个值意味着您需要了解该值类型的实现细节,尤其是它如何管理内存。

因此,您需要以一种或另一种方式(通过友谊,或者更公开的接口)发布这些实现细节,这通常是不可取的。无论哪种方式,您都会在类型之间获得紧密耦合,这是无法避免的。

即使可能,我也不认为这会像您想象的那样普遍有用。

【讨论】:

  • 感谢您的回答。我发现没有通用的解决方案,并且必须以某种方式将实现细节传输给另一个类,这就是move_ptr 具有通用自定义功能的原因。但是正如您所说,这可能还不够,因为在没有额外信息的情况下仍然不会传输有关释放的信息。我想这实际上会迫使我们超越,除了首先实现vectorunique_ptr 之外几乎没有其他选择,这听起来还不错。
  • 我不介意在其中一个方向上耦合。 supervector 已经依赖于 vector (由于示例构造函数的签名,它已经依赖于接口)。另外我不介意超向量依赖于向量的实现
猜你喜欢
  • 1970-01-01
  • 2016-12-16
  • 2016-04-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-09
相关资源
最近更新 更多