Как сделать класс подвижным для других классов? - PullRequest
0 голосов
/ 03 августа 2020

Допустим, у меня есть класс 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 в другой класс).

Предварительные работы:

Возможные решения:

  1. Сделайте SuperVector a friend из 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, нарушить инварианты и т. д. c.

    Теперь более сложные и нетривиальные варианты.

  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
       }
    

    Я уверен, что есть крайние случаи, которые я не рассматриваю с помощью этого упрощенного c кода , но я надеюсь, что основная идея ясна.

    Исправления приветствуются.

    Имеет ли смысл этот умный указатель или что-то вроде этого «указателя диспетчера перемещений» уже где-то реализовано ?

    Я вижу, что это похоже на std::unique_ptr, за исключением того, что перемещение осуществляется с помощью интеллектуального указателя «скопировано из», а также настраиваемое действие происходит только при назначении (обычному указателю).

    Возможно, того же эффекта можно достичь с помощью std::unique_ptr, но я не понимаю, как.

Это конкретное приложение к этому более абстрактному вопросу: Exact соответствие между ссылками на r-значение и указателями?

Ответы [ 2 ]

2 голосов
/ 03 августа 2020

Вы рассмотрели все возможности, и прямой ответ на ваш вопрос заключается в том, что не существует общего протокола.

В этом ответе рассматриваются несколько мест в std :: lib, которые позволяют передача права собственности несвязанным типам.

unique_ptr

auto p = up.release();

unique_ptr up отказался от владения своим ресурсом, и теперь клиент

unique_lock

auto m = ul.release();

unique_lock ul отказался от владения заблокированным состоянием своего мьютекса, и клиент теперь отвечает за это.

Ассоциативные и неупорядоченные контейнеры

Функция-член extract ищет отдельный элемент (по ключу или итератору) и освобождает право владения единственный узел в объекте, подобном интеллектуальному указателю, с именем container::node_type. Этот «умный указатель» имеет копию распределителя, поэтому он знает, как разрушить себя. Он также предоставляет клиенту неконстантный доступ к содержащемуся элементу. Доступен только константный доступ к ключевой части элемента, когда она принадлежит контейнеру.

2 голосов
/ 03 августа 2020

Общего решения в принципе быть не может. Выход за пределы значения в основном означает, что вам необходимо знать детали реализации этого типа значения, в частности, как он управляет памятью.

Итак, вам нужно sh опубликовать эти детали реализации тем или иным способом (через дружба, или тем более publi c interface), что вообще нежелательно. В любом случае вы получите тесную связь между типами, и этого просто невозможно избежать.

Я также не думаю, что даже если бы это было возможно, это было бы настолько полезно, как вы думаете.

...