【发布时间】: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 转移到一个不相关的类)。
前期工作:
我能想到的解决方案是:
-
让
SuperVector成为vector的朋友,然后简单地实现“进入”的移动SuperVector(vector&& v) : data_begin_{v.data()}, data_end_{v.data() + v.size()}{v.size_ = 0;}这个问题是友谊增加了很多耦合,它不是一个通用的解决方案。
-
提供对向量内部表示的更多访问权限(特别是使其可分配)。
int& vector::size(){return size_;}SuperVector(vector&& v) : data_begin_{v.data()}, data_end_{v.data() + v.size()}{v.size() = 0;}这真的很糟糕,因为任何人都可以更改
size、破坏不变量等。现在是更复杂和重要的选项。
-
类似 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似乎“过早地”处于从状态移动的状态,远在对象真正从(客户端)其他类中移动之前。最后,
-
也许我缺少的是某种新的智能指针,它可以延迟移动到以后的时间。 我在这里称它为
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?
【问题讨论】:
-
也许你想多了?你真的需要两个不同的向量类,唯一的区别是存储大小和存储指向缓冲区末尾的指针吗?
-
@HolyBlackCat,不,这只是更复杂数据结构的简化示例。即使表示相同
data_ + size_我也会遇到同样的问题。您建议的非过度思考的解决方案基本上是SuperVector包含一个vector并完成它。 -
如果我必须这样做,我会选择友谊。这是不暴露脏不安全函数的唯一方法。此外,您为什么关心“耦合”?如果您必须将向量移动到 SuperVector,则它们 耦合/相关。如果不建立它们之间的关系,您就无法从一个移动到另一个。这对我来说没有意义。
-
vector类需要公开一种方法来释放其内部管理的资源(就像std::unique_ptr对release 所做的那样),如果它没有这种可能性,那么您要么需要存储@ 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