Допустим, у меня есть класс 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;}
Итак далеко, по модулю опечаток и педанти c комментарии, вот и все.
Однако я хотел бы сделать класс перемещаемым другими классами, которые не связаны с моим 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
a friend из 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
, нарушить инварианты и т. д. c.
Теперь более сложные и нетривиальные варианты.
как 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
}
Я уверен, что есть крайние случаи, которые я не рассматриваю с помощью этого упрощенного c кода , но я надеюсь, что основная идея ясна.
Исправления приветствуются.
Имеет ли смысл этот умный указатель или что-то вроде этого «указателя диспетчера перемещений» уже где-то реализовано ?
Я вижу, что это похоже на std::unique_ptr
, за исключением того, что перемещение осуществляется с помощью интеллектуального указателя «скопировано из», а также настраиваемое действие происходит только при назначении (обычному указателю).
Возможно, того же эффекта можно достичь с помощью std::unique_ptr
, но я не понимаю, как.
Это конкретное приложение к этому более абстрактному вопросу: Exact соответствие между ссылками на r-значение и указателями?