ПочемуObserver_ptr не обнуляется после хода?

Почему observer_ptr обнуляется после операции перемещения?

Он правильно установлен на nullptr в своей конструкции по умолчанию, и это имеет смысл (и предотвращает указание на мусор).

И, соответственно, он должен обнуляться при std::move()'d from, так же как и std::string, std::vector и т.д.

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


ИЗМЕНИТЬ

Как отметил @JonathanWakely в комментариях (и это связано с вышеупомянутым вопросом):

если observer_ptr было нулевым после перемещения, его можно использовать для реализации правила нуля для типов, которые имеют член-указатель. Это очень полезная функция.


person Mr.C64    schedule 10.03.2014    source источник
comment
Почему должно быть обнулено? Он не должен владеть тем, на что указывает.   -  person juanchopanza    schedule 11.03.2014
comment
Обнуление обходится дороже, чем не делать этого, и в любом случае вам не следует полагаться на значение «перемещено из».   -  person Alan Stokes    schedule 11.03.2014
comment
Вау, observer_ptr выглядит самым нелепым предложением, которое я когда-либо видел.   -  person user541686    schedule 11.03.2014
comment
@juanchopanza: он не владеет этой вещью, верно, на самом деле он не должен ее УДАЛЯТЬ. Я говорю о том, чтобы просто установить его значение на nullptr.   -  person Mr.C64    schedule 11.03.2014
comment
@AlanStokes: я совершенно не согласен. Перемещенный объект должен находиться в безопасном состоянии.   -  person Mr.C64    schedule 11.03.2014
comment
@AlanStokes, этого нельзя сказать обо всех типах. Для некоторых типов, таких как unique_ptr, unique_lock и future, вам абсолютно необходимо полагаться на значение, с которого было перемещено, иначе вы получите несколько владельцев! Для пользовательских типов с указателями, не принадлежащими владельцам, очень полезно иметь тупой тип указателя null-after-move, см. stackoverflow.com /a/22307926/981959   -  person Jonathan Wakely    schedule 11.03.2014
comment
Обнуление перемещенной вещи предполагает, что право собственности на вещь было передано. Но нет права собственности на передачу, поэтому нет смысла обнулять.   -  person juanchopanza    schedule 11.03.2014
comment
@juanchopanza, если observer_ptr было нулевым после перемещения, его можно использовать для реализации правила нуля для типов, у которых есть член-указатель. Это очень полезная функция. Любой тип, который имеет член-указатель и что-то делает с ним в деструкторе, если указатель не нулевой, может использовать tidy_ptr (например, unique_lock, типы, подобные ScopeGuard, и т. д.)   -  person Jonathan Wakely    schedule 11.03.2014
comment
@JonathanWakely: точно!   -  person Mr.C64    schedule 11.03.2014
comment
Как я только что прокомментировал ваш другой вопрос, вы ошибаетесь в отношении std::string и std::vector. Нет гарантии, что string или vector пусты после хода. Они остаются в действительном, но неуказанном состоянии. Некоторые типы, такие как unique_ptr, shared_ptr, unique_lock и future, обеспечивают более надежную гарантию пребывания в известном состоянии после перемещения, контейнеры этого не гарантируют.   -  person Jonathan Wakely    schedule 11.03.2014
comment
@JonathanWakely Это было бы полезно, но у меня было ощущение, что оно не предназначено для такого рода вещей. С другой стороны, я не вижу особой пользы в его нынешнем предлагаемом виде.   -  person juanchopanza    schedule 11.03.2014
comment
@juanchopanza, его текущее использование заключается в том, чтобы явно указать, что вы передаете указатель, не являющийся владельцем, что полезно, потому что неясно, что void f(X*) намеревается делать с указателем (получит ли он право собственности?) Это полезно. Я просто думаю, что было бы полезнее, если бы он также имел одно дополнительное свойство и был бы нулевым после перемещения, иначе мне нужен другой тупой интеллектуальный указатель, который точно такой же, за исключением того, что он является нулевым после перемещения. .   -  person Jonathan Wakely    schedule 11.03.2014
comment
@JonathanWakely Я согласен и понимаю мотивацию. Но с обнуляющим ходом его нельзя было использовать в дальнейших глупых типах наблюдателей, которые не требуют умных действий при перемещении. Но, может быть, потребность в этом сверхтупом умном указателе не так актуальна, как в том, который вы описываете.   -  person juanchopanza    schedule 11.03.2014
comment
@juanchopanza, я не могу придумать никаких полезных типов, которые требуют, чтобы указатель имел свое старое значение после перемещения, но если вы этого хотите, используйте встроенный тип указателя.   -  person Jonathan Wakely    schedule 11.03.2014
comment
@JonathanWakely Я тоже, но, учитывая то, как это объясняется в предложении, я бы не ожидал, что оно обнулится при перемещении. Все, что я хочу сказать, это то, что такое поведение кажется мне самосогласованным, а не то, что это самый полезный тупой умный указатель. Я бы точно пригодился больше для твоего tidy_ptr.   -  person juanchopanza    schedule 11.03.2014


Ответы (3)


Кажется, что многие люди поначалу упускают из виду суть и полезность этой идеи.

Рассмотреть возможность:

template<typename Mutex>
class unique_lock
{
  Mutex* pm;

public:
  unique_lock() : pm() { }

  unique_lock(Mutex& m) : pm(&m) { }

  ~unique_lock() { if (pm) pm->unlock(); }

  unique_lock(unique_lock&& ul) : pm(ul.pm) { ul.pm = nullptr; }

  unique_lock& operator=(unique_lock&& ul)
  {
    unique_lock(std::move(ul)).swap(*this);
    return *this;
  }

  void swap(unique_lock& ul) { std::swap(pm, ul.pm); }
};

С «глупым» интеллектуальным указателем, который имеет значение null-on-default-construction и null-after-move, вы можете по умолчанию использовать три специальные функции-члены, поэтому он становится:

template<typename Mutex>
class unique_lock
{
  tidy_ptr<Mutex> pm;

public:
  unique_lock() = default;                            // 1

  unique_lock(Mutex& m) : pm(&m) { }

  ~unique_lock() { if (pm) pm->unlock(); }

  unique_lock(unique_lock&& ul) = default;            // 2

  unique_lock& operator=(unique_lock&& ul) = default; // 3

  void swap(unique_lock& ul) { std::swap(pm, ul.pm); }
};

Вот почему полезно иметь тупой интеллектуальный указатель, не являющийся владельцем, который становится нулевым после перемещения, например tidy_ptr

Но observer_ptr — это только конструкция null-on-default, поэтому, если она стандартизирована, она будет полезна для объявления функции, принимающей указатель, не являющийся владельцем, но она не будет полезна для классов, подобных приведенному выше, поэтому я Мне все еще нужен другой тип немого указателя, не являющийся владельцем. Наличие двух типов тупых умных указателей, не являющихся владельцами, кажется чуть ли не хуже, чем их отсутствие!

person Jonathan Wakely    schedule 10.03.2014
comment
Я отметил это как ответ. Похоже, что в текущем предложении observer_ptr не учитывались полезные аспекты, на которые вы здесь указали. Я пока не могу найти никакого обоснования для текущего предложения observer_ptr. - person Mr.C64; 11.03.2014
comment
Предложение описывает обоснование. - person Jonathan Wakely; 11.03.2014

Таким образом, конструкторы перемещения предназначены для удешевления конструкторов копирования в определенных случаях.

Давайте напишем, какими должны быть эти конструкторы и деструкторы: (Это небольшое упрощение, но для данного примера это нормально).

observer_ptr() {
    this->ptr == nullptr;
}

observer_ptr(T *obj) {
    this->ptr = obj;
}

observer_ptr(observer_ptr<T> const & obj) {
    this->ptr = obj.ptr;
}

~observer_ptr() {
}

Вы предполагаете, что класс предоставляет конструктор перемещения, который выглядит так:

observer_ptr(observer_ptr<T> && obj) {
    this->ptr = obj.ptr;
    obj.ptr = null;
}

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

А как насчет std::vector?

std::vector при копировании фактически копирует массив, который он поддерживает. Итак, конструктор копирования std::vector выглядит примерно так:

vector(vector<T> const & obj) {
    for (auto const & elem : obj)
        this->push_back(elem);
}

Конструктор перемещения для std::vector может оптимизировать это. Это возможно, потому что эта память может быть украдена у obj. В std::vector это действительно полезно сделать.

vector(vector<T> && obj) {
    this->data_ptr = obj.data_ptr;
    obj.data_ptr = nullptr;
    obj.size = 0;
}
person Bill Lynch    schedule 10.03.2014
comment
+1, хотя я предлагаю использовать nullptr вместо null в С++ 11. - person Marc Claesen; 11.03.2014
comment
Помимо дешевизны операции перемещения, это не имело бы смысла семантически. Наблюдатель_ptr как раз это. Он не интересуется и не знает о времени жизни путеводителя. Он существует только для целей документации. - person juanchopanza; 11.03.2014
comment
@juanchopanza Разве эти свойства не сделают его лучше std::weak_ptr<T>? - person πάντα ῥεῖ; 11.03.2014
comment
@πάνταῥεῖ std::weak_ptr слишком умен: он позволяет вам проверить действительность и получить совместное владение. - person juanchopanza; 11.03.2014
comment
@juanchopanza 'std::weak_ptr слишком умный', что на самом деле заставило меня смеяться :-D ... - person πάντα ῥεῖ; 11.03.2014
comment
Итак, конструкторы перемещения предназначены для удешевления конструкторов копирования в определенных случаях. Это одно из применений, но не единственное. Они также позволяют передавать некопируемые вещи по значению, например. std::thread, std::unique_ptr, std::future и т. д., и все эти вещи полагаются на то, что они пусты после хода. - person Jonathan Wakely; 11.03.2014

Помимо незначительного влияния на производительность обнуления перемещенного из observer_ptr, которое легко обойти (копировать вместо перемещения), основная причина, вероятно, заключалась в том, чтобы максимально точно имитировать поведение обычных указателей, следуя принцип наименьшего удивления.

Однако существует потенциально гораздо более серьезная проблема с производительностью: по умолчанию функции перемещения позволяют observer_ptr быть легко копируется. Тривиальная копируемость позволяет копировать объект с помощью std::memcpy. Если бы observer_ptr не было тривиально копируемым, то не было бы и никаких классов с членом данных observer_ptr, что приводило бы к снижению производительности, которое ниспадало бы каскадом вниз по иерархии композиционных классов.

Я понятия не имею, какие улучшения производительности можно получить с помощью оптимизации std::memcpy, но они, вероятно, более значительны, чем вышеупомянутая незначительная проблема с производительностью.

person Joseph Thomson    schedule 09.03.2016