Когда я должен использовать сырые указатели над умными указателями? - PullRequest
55 голосов
/ 13 июля 2011

После прочтения этого ответа похоже, что лучше всего использовать интеллектуальных указателей в максимально возможной степени и сократить использование «обычных» / необработанных указателей доминимум.

Это правда?

Ответы [ 7 ]

80 голосов
/ 13 июля 2011

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

  • Нет собственности, поэтому вы не знаете, какой умный указатель передать
  • Если вы передадите конкретный указатель, например shared_ptr, вы не сможете передать, скажем, scoped_ptr

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

Пример1: * * тысяча двадцать один

void PrintObject(shared_ptr<const Object> po) //bad
{
    if(po)
      po->Print();
    else
      log_error();
}

void PrintObject(const Object* po) //good
{
    if(po)
      po->Print();
    else
      log_error();
}

Пример2:

Object* createObject() //bad
{
    return new Object;
}

some_smart_ptr<Object> createObject() //good
{
   return some_smart_ptr<Object>(new Object);
}
14 голосов
/ 13 июля 2011

Использование интеллектуальных указателей для управления собственностью является правильным решением.И наоборот, при использовании необработанных указателей там, где владение не , проблема не неправильная.

Вот некоторые совершенно законные способы использования необработанных указателей (помните, что всегда предполагается, что онине принадлежат):

, где они конкурируют со ссылками

  • передача аргумента;но ссылки не могут быть нулевыми, поэтому предпочтительнее
  • в качестве членов класса обозначать ассоциацию, а не композицию;обычно предпочтительнее ссылок, потому что семантика присваивания более проста и, кроме того, инвариант, установленный конструкторами, может гарантировать, что они не являются 0 для времени жизни объекта
  • как дескриптор (возможнополиморфный) объект, принадлежащий где-то еще;ссылки не могут быть нулевыми, поэтому они снова предпочтительны
  • std::bind использует соглашение, в котором переданные аргументы копируются в результирующий функтор;однако std::bind(&T::some_member, this, ...) только копирует указатель, тогда как std::bind(&T::some_member, *this, ...) копирует объект;std::bind(&T::some_member, std::ref(*this), ...) является альтернативой

, где они не конкурируют со ссылками

  • в качестве итераторов!
  • передача аргумента опционально параметры;здесь они конкурируют с boost::optional<T&>
  • как дескриптор (возможно, полиморфного) объекта, принадлежащего где-то еще, когда они не могут быть объявлены на месте инициализации;опять же, конкурируя с boost::optional<T&>

В качестве напоминания, почти всегда неправильно писать функцию (которая не является конструктором или членом функции, который, например, вступает во владение), который принимает умный указатель, если толькоон, в свою очередь, передает его конструктору (например, он корректен для std::async, потому что семантически он близок к вызову конструктора std::thread).Если это синхронно, умный указатель не нужен.


Подводя итог, вот фрагмент, демонстрирующий несколько из вышеперечисленных вариантов использования.Мы пишем и используем класс, который применяет функтор к каждому элементу std::vector<int> при записи некоторого вывода.

class apply_and_log {
public:
    // C++03 exception: it's acceptable to pass by pointer to const
    // to avoid apply_and_log(std::cout, std::vector<int>())
    // notice that our pointer would be left dangling after call to constructor
    // this still adds a requirement on the caller that v != 0 or that we throw on 0
    apply_and_log(std::ostream& os, std::vector<int> const* v)
        : log(&os)
        , data(v)
    {}

    // C++0x alternative
    // also usable for C++03 with requirement on v
    apply_and_log(std::ostream& os, std::vector<int> const& v)
        : log(&os)
        , data(&v)
    {}
    // now apply_and_log(std::cout, std::vector<int> {}) is invalid in C++0x
    // && is also acceptable instead of const&&
    apply_and_log(std::ostream& os, std::vector<int> const&&) = delete;

    // Notice that without effort copy (also move), assignment and destruction
    // are correct.
    // Class invariants: member pointers are never 0.
    // Requirements on construction: the passed stream and vector must outlive *this

    typedef std::function<void(std::vector<int> const&)> callback_type;

    // optional callback
    // alternative: boost::optional<callback_type&>
    void
    do_work(callback_type* callback)
    {
        // for convenience
        auto& v = *data;

        // using raw pointers as iterators
        int* begin = &v[0];
        int* end = begin + v.size();
        // ...

        if(callback) {
            callback(v);
        }
    }

private:
    // association: we use a pointer
    // notice that the type is polymorphic and non-copyable,
    // so composition is not a reasonable option
    std::ostream* log;

    // association: we use a pointer to const
    // contrived example for the constructors
    std::vector<int> const* data;
};
6 голосов
/ 13 июля 2011

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

Однако нам действительно не хватает «пустых» умных указателей, которые не подразумевают какого-либо понятия собственности.

template <typename T>
class ptr // thanks to Martinho for the name suggestion :)
{
public:
  ptr(T* p): _p(p) {}
  template <typename U> ptr(U* p): _p(p) {}
  template <typename SP> ptr(SP const& sp): _p(sp.get()) {}

  T& operator*() const { assert(_p); return *_p; }
  T* operator->() const { assert(_p); return _p; }

private:
  T* _p;
}; // class ptr<T>

Это, действительно, самая простая версия из всех возможных интеллектуальных указателей: тип, который документирует, что ему не принадлежит ресурс, на который он указывает.

4 голосов
/ 13 июля 2011

Один случай, когда подсчет ссылок (используемый, в частности, shared_ptr) не работает, - это когда вы создаете цикл из указателей (например, A указывает на B, B указывает на A или A-> B-> C-> А или т. Д.). В этом случае ни один из объектов никогда не будет автоматически освобожден, потому что все они хранят ссылки друг на друга больше нуля.

По этой причине всякий раз, когда я создаю объекты, имеющие отношения родитель-потомок (например, дерево объектов), я буду использовать shared_ptrs в родительских объектах для хранения своих дочерних объектов, но если дочерним объектам требуется указатель назад для их родителей я буду использовать простой указатель C / C ++ для этого.

1 голос
/ 10 февраля 2016

Я думаю, что здесь был дан немного более подробный ответ: Какой тип указателя мне использовать, когда?

Из этой ссылки извлечено: "Использовать тупые указатели (необработанные указатели) илиссылки на не владеющих ссылками на ресурсы и когда вы знаете, что ресурс переживет ссылающийся объект / область. "(выделено жирным шрифтом из оригинала)

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

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_current_employees_with(const employee_t* p_new_employee, std::list<employee_t>& employee_list) {
    employee_list.clear();
    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("John", "Smith"));
    current_employee_list.push_back(employee_t("Julie", "Jones"));
    employee_t* p_person_who_convinces_boss_to_rehire_him = &(current_employee_list.front());

    replace_current_employees_with(p_person_who_convinces_boss_to_rehire_him, current_employee_list);
}

К своему удивлению, функция replace_current_employees_with() может непреднамеренно вызвать освобождение одного из ее параметров до того, как она его закончит.

Так что, даже если она можетна первый взгляд кажется, что функция replace_current_employees_with() не нуждается в владении своими параметрами, ей нужна какая-то защита от возможности коварного освобождения ее параметров до того, как они закончат их использовать.Самое простое решение - фактически взять (временно совместно) владеть параметром (ами), предположительно через shared_ptr.

Но если вы действительно не хотите вступать во владение, теперь есть безопасный вариант- и это бесстыдная часть ответа - « зарегистрированных указателей ».«зарегистрированные указатели» - это интеллектуальные указатели, которые ведут себя как необработанные указатели, за исключением того, что они (автоматически) устанавливаются на null_ptr, когда целевой объект уничтожается, и по умолчанию выдают исключение, если вы пытаетесь получить доступ к объекту, который ужебыл удален.

Также обратите внимание, что зарегистрированные указатели могут быть «отключены» (автоматически заменены их необработанными аналогами указателей) с помощью директивы времени компиляции, что позволяет использовать их (и нести накладные расходы) в debug / test /только бета-режимы.Так что вам действительно придется прибегать к реальным необработанным указателям довольно редко.

1 голос
/ 13 июля 2011

Несколько случаев, когда вы можете захотеть использовать указатели:

  • Функциональные указатели (очевидно, не умный указатель)
  • Определение собственного умного указателя или контейнера
  • Работа с низкоуровневым программированием, где необработанные указатели имеют решающее значение
  • Распад из необработанных массивов
0 голосов
/ 13 июня 2017

Это правда.Я не вижу преимуществ необработанных указателей над умными указателями, особенно в сложных проектах.

Для временного и легкого использования необработанные указатели хороши.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...