如何使一个班级可以被其他班级移动?

时间:2020-08-03 08:13:26

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

假设我有一个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;}
}

如果要使该类可移动,可以添加一个move构造函数。

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

到目前为止,仅是拼写错误和书呆子注释。

不过,我想使该类可以通过与我的vector无关的 other 类进行移动。 我知道这基本上需要类似于move构造函数的代码,但需要一个独立的类。

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. SuperVectorvector朋友,然后简单地实现“移入”动作

       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()&mdata()&mdata()&&data()&&。)

    请注意,此移动的副作用是由智能指针承担的。

    并将其用作

       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?

2 个答案:

答案 0 :(得分:2)

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

因此,您需要以一种或另一种方式(通过友谊,甚至是更公共的界面)发布这些实现细节,这通常是不希望的。无论哪种方式,您都会在类型之间建立紧密的联系,而这是不可避免的。

我也不认为即使有可能,它也不会像您认为的那样有用。

答案 1 :(得分:2)

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

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

unique_ptr

auto p = up.release();

unique_ptr up已放弃对其资源的所有权,而客户端现在对此负有责任。

unique_lock

auto m = ul.release();

unique_lock ul放弃了其互斥锁的锁定状态的所有权,客户端现在对此负责。

关联和无序容器

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