Подтвердить что ты не робот

Использование observer_ptr

Что такое точка конструкции std::observer_ptr в технической спецификации библиотеки V2?

Мне кажется, что все, что он делает, это обрезать голый T*, который кажется излишним, если он не добавит никакой безопасности в динамической памяти.

Во всем моем коде я использую std::unique_ptr, где мне нужно явно владеть объектом и std::shared_ptr, где я могу предоставить право собственности на объект.

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

std::observer_ptr, конечно, не гарантирует гарантии времени жизни наблюдаемого объекта.

Если бы он был построен из std::unique_ptr или std::shared_ptr, я бы увидел использование в такой структуре, но любой код, который просто использует T*, вероятно, будет продолжать делать это, и если они планируют при перемещении на что угодно было бы std::shared_ptr и/или std::unique_ptr (в зависимости от использования).


Для простой примерной функции:

template<typename T>
auto func(std::observer_ptr<T> ptr){}

Где было бы полезно, если бы он остановил интеллектуальные указатели на уничтожение их сохраненного объекта во время их наблюдения.

Но если я хочу наблюдать a std::shared_ptr или std::unique_ptr, я должен написать:

auto main() -> int{
    auto uptr = std::make_unique<int>(5);
    auto sptr = std::make_shared<int>(6);
    func(uptr.get());
    func(sptr.get());
}

Это делает его более безопасным, чем:

template<typename T>
auto func(T *ptr){}

Итак, в чем заключается использование этой новой структуры?

Это только для самодокументирующего источника?

4b9b3361

Ответ 1

Предложение дает понять, что это просто для самостоятельной документации:

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

Ответ 2

Если вам нужен общий доступ, но не общий доступ.

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

Когда raw-указатель управляется интеллектуальным указателем, его очистка гарантируется, и поэтому в течение срока действия интеллектуального указателя имеет смысл получить доступ к фактическим данным через необработанный указатель, которым управляет интеллектуальный указатель.

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

При передаче управляемого необработанного указателя в качестве аргумента в параметр функции std::observer_ptr мы знаем, что функция не будет delete it.

Это способ для функции сказать "дайте мне свой указатель, я не буду вмешиваться в его распределение, я просто буду использовать его для наблюдения".

Кстати, я не увлекаюсь именем std::observer_ptr, потому что это означает, что вы можете смотреть, но не трогать. Но это не так. Я бы пошел с чем-то более похожим на access_ptr.

Примечание:

Это другой случай использования от std::shared_ptr. std::shared_ptr заключается в совместном использовании, и он должен использовать только, когда вы не можете определить, какой объект-владелец сначала выйдет из области.

С другой стороны, std::observer_ptr предназначен для совместного доступа, но не для владения.

Нельзя использовать std::shared_ptr просто для совместного доступа, потому что это может быть очень неэффективным.

Итак, независимо от того, управляете ли вы своим целевым указателем с помощью std::unique_ptr или std::shared_ptr, все еще используется префикс для raw-указателей и, следовательно, рациональный для std::observer_ptr.

Ответ 3

Это только для самостоятельной документации источника?

Да.

Ответ 4

Из предложения строгий слабый порядок во всех реализациях, что не гарантируется для двух произвольных указателей. Это связано с тем, что operator< реализуется в терминах std::less для observer_ptr (как с std::unique_ptr и std::shared_ptr).

observer_ptr<void> представляется неподдерживаемым, что может способствовать использованию более безопасных решений (например, std::any и std::variant)

Ответ 5

Одним из приятных последствий использования std::observer_ptr над необработанными указателями является то, что он обеспечивает лучшую альтернативу запутанному и подверженному ошибкам синтаксису экземпляра нескольких указателей, унаследованному от C.

std::observer_ptr<int> a, b, c;

является улучшением на

int *a, *b, *c;

который немного странно с точки зрения С++ и может быть легко опечатан как

int* a, b, c;

Ответ 6

Да, точка std::observer_ptr в значительной степени просто "самодокументируется", и это действительно допустимый конец сам по себе. Но следует отметить, что, возможно, это не делает большой работы, поскольку это не очевидно, что такое "наблюдатель". Во-первых, как указывает Галик, некоторые из них, по-видимому, подразумевают обязательство не изменять цель, которая не является целью, поэтому лучше было бы назвать имя access_ptr. И, во-вторых, без каких-либо классификаторов имя будет означать одобрение его "нефункционального" поведения. Например, можно считать, что std::weak_ptr является типом указателя "наблюдателя". Но std::weak_ptr размещает случай, когда указатель выделяет целевой объект, предоставляя механизм, который позволяет попыткам доступа к объекту (освобожденному) для обеспечения безопасности. std::observer_ptr реализация не предусматривает этого случая. Таким образом, возможно, raw_access_ptr будет лучшим названием, поскольку оно лучше укажет на его функциональный недостаток.

Итак, как вы справедливо спрашиваете, какой смысл этого функционально бросить вызов "не владеющему" указателю? Основная причина - это, вероятно, производительность. Многие программисты на C++ воспринимают накладные расходы std::share_ptr слишком высокими и поэтому просто будут использовать необработанные указатели, когда им нужны указатели "наблюдателя". Предлагаемая std::observer_ptr попытка обеспечить небольшое улучшение четкости кода при приемлемых эксплуатационных расходах. В частности, нулевая стоимость исполнения.

К сожалению, существует, по-видимому, широко распространенный, но, на мой взгляд, нереалистичный оптимизм в отношении того, насколько безопасно использовать исходные указатели как указатели "наблюдателя". В частности, хотя легко сформулировать требование о том, что целевой объект должен пережить std::observer_ptr, не всегда легко быть абсолютно уверенным в том, что он выполняется. Рассмотрим этот пример:

struct employee_t {
    employee_t(const std::string& first_name, const std::string& last_name) : m_first_name(first_name), m_last_name(last_name) {}
    std::string m_first_name;
    std::string m_last_name;
};

void replace_last_employee_with(const std::observer_ptr<employee_t> p_new_employee, std::list<employee_t>& employee_list) {
    if (1 <= employee_list.size()) {
        employee_list.pop_back();
    }
    employee_list.push_back(*p_new_employee);
}

void main(int argc, char* argv[]) {
    std::list<employee_t> current_employee_list;
    current_employee_list.push_back(employee_t("Julie", "Jones"));
    current_employee_list.push_back(employee_t("John", "Smith"));

    std::observer_ptr<employee_t> p_person_who_convinces_boss_to_rehire_him(&(current_employee_list.back()));
    replace_last_employee_with(p_person_who_convinces_boss_to_rehire_him, current_employee_list);
}

Автору функции replace_last_employee_with(), возможно, никогда не приходило в голову, что ссылка на новый прокат также может быть ссылкой на существующего сотрудника, который должен быть заменен, и в этом случае функция может непреднамеренно вызвать цель ее std::observer_ptr<employee_t>, который должен быть освобожден до его использования.

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

Если замена параметра std::observer_ptr<employee_t> на std::shared_ptr или std::weak_ptr по какой-либо причине неприемлема, теперь есть еще одна безопасная опция - и это бесстыдная часть ответчика - " зарегистрированных указателей". "зарегистрированные указатели" - это интеллектуальные указатели, которые ведут себя так же, как и исходные указатели, за исключением того, что они (автоматически) установлены на null_ptr, когда целевой объект уничтожается, и по умолчанию генерирует исключение, если вы пытаетесь получить доступ к объекту, который имеет уже удалены. Обычно они быстрее, чем std:: shared_ptrs, но если ваши требования к производительности действительно строгие, зарегистрированные указатели могут быть "отключены" (автоматически заменены их raw pointer) с директивой времени компиляции, позволяя им использовать (и нести накладные расходы) только в режимах отладки/тестирования/бета-версии.

Итак, если будет указатель "наблюдателя", основанный на исходных указателях, то, возможно, должен быть один, основанный на зарегистрированных указателях и, возможно, предложенный OP, один из которых основан на std:: shared_ptr.