Прочитав этот ответ, похоже, что рекомендуется использовать как можно больше умных указателей и свести к минимуму использование "обычных"/необработанных указателей.
Это правда?
Прочитав этот ответ, похоже, что рекомендуется использовать как можно больше умных указателей и свести к минимуму использование "обычных"/необработанных указателей.
Это правда?
Нет, это неправда. Если функция нуждается в указателе и не имеет ничего общего с правом собственности, я твердо верю, что регулярный указатель должен быть передан по следующим причинам:
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);
}
Использование интеллектуальных указателей для управления собственностью - это правильная вещь. И наоборот, использование необработанных указателей, где владение не является проблемой, не соответствует не.
Вот некоторые совершенно законные способы использования необработанных указателей (помните, всегда считается, что они не владеют):
где они конкурируют со ссылками
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 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;
};
Использование интеллектуальных указателей всегда рекомендуется, поскольку они четко документируют право собственности.
Однако мы действительно скучаем, это "чистый" умный указатель, который не подразумевает какого-либо понятия собственности.
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>
Это действительно самая простая версия любого умного указателя, который может существовать: тип, который документирует, что он не владеет ресурсом, на который он указывает.
Один экземпляр, где подсчет ссылок (в частности, shared_ptr) будет разрушен, - это когда вы создаете цикл из указателей (например, A указывает на B, B указывает на A или A- > B- > C- > A, или и т.д.). В этом случае ни один из объектов никогда не будет автоматически освобожден, потому что все они поддерживают друг друга, количество ссылок больше нуля.
По этой причине, всякий раз, когда я создаю объекты с родительско-дочерними отношениями (например, с деревом объектов), я буду использовать shared_ptrs в родительских объектах для хранения своих дочерних объектов, но если дочерним объектам нужен указатель назад к их родителям, я буду использовать простой указатель C/С++ для этого.
Несколько случаев, когда вы можете использовать указатели:
Я думаю, что здесь был дан более подробный ответ: Какой тип указателя я использую, когда?
Выдержка из этой ссылки: "Используйте немые указатели (необработанные указатели) или ссылки для ссылок, не относящихся к владельцам, и когда вы знаете, что ресурс перейдет в ссылку объект/область действия". (выделено жирным шрифтом от оригинала)
Проблема в том, что если вы пишете код для общего использования, не всегда легко быть абсолютно уверенным, что объект перейдет в исходный указатель. Рассмотрим этот пример:
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
, когда целевой объект уничтожается, и по умолчанию генерирует исключение, если вы пытаетесь получить доступ к объекту, который уже был удален.
Также обратите внимание, что зарегистрированные указатели могут быть "отключены" (автоматически заменены на их исходный указатель) с директивой времени компиляции, что позволяет им использовать (и нести накладные расходы) только в режимах отладки/тестирования/бета. Таким образом, вам действительно нужно очень редко прибегать к фактическим исходным указателям.
Это правда. Я не вижу преимуществ сырых указателей над умными указателями, особенно в сложном проекте.
Для временного и легкого использования исходные указатели в порядке.