Почему и когда мне нужно поставлять собственный дебит?
Почему и когда мне нужно поставлять собственный дебит? Не ключевое слово delete
достаточно достаточно?
Если вы используете интеллектуальный указатель для управления ресурсом, отличным от памяти, выделенной new
, не забудьте передать дебетер.
Обновить:
Как спрашивается в комментариях, причина, по которой я неясна о цитируемом тексте и примере, заключается в том, что я неправильно думал о чем-то, что я думал о умном указателе, только придумано для/связанного с управлением динамической памятью. Таким образом, пример использует интеллектуальный указатель для управления нединамической памятью, что меня смущает.
Хорошее объяснение от старшего:
Умный указатель совсем не заботится о том, что это динамическая память как таковая. Это просто способ отслеживать что-то, пока оно вам нужно, и уничтожать это, когда оно выходит за рамки. Точка упоминания файловых дескрипторов, сетевых подключений и т.д. Заключалась в том, что они не являются динамической памятью, но умный указатель может управлять ими в любом случае.
C++ Primer 5th принимает псевдо-сетевое соединение (не определяют деструкторы) для иллюстрации.
Плохой:
struct destination; // represents what we are connecting to
struct connection; // information needed to use the connection
connection connect(destination*); // open the connection
void disconnect(connection); // close the given connection
void f(destination &d /* other parameters */)
{
// get a connection; must remember to close it when done
connection c = connect(&d);
// use the connection
// if we forget to call disconnect before exiting f, there will be no way to closes
}
Хорошо:
void end_connection(connection *p) { disconnect(*p); }
void f(destination &d /* other parameters */)
{
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection);
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
}
Полный скриншот контекста (я очищаю некоторые несвязанные тексты):
Ответы
Ответ 1
Вам необходимо предоставить собственное удаление для создания интеллектуального указателя, когда стандартное delete
не подходит для освобождения, освобождения, отбрасывания или иного распоряжения ресурсом, срок жизни которого регулируется интеллектуальным указателем.
Типичным использованием умного указателя является выделение памяти как ресурса, управляемого интеллектуальным указателем, так что, когда интеллектуальный указатель выходит за пределы области видимости, управляемый ресурс в этом случае отбрасывается с помощью оператора delete
.
Стандартный оператор delete
выполняет две функции: (1) вызывает деструктор объекта, чтобы позволить объекту делать какую-либо очистку, которую он должен выполнить, прежде чем выделенная или освобожденная память освобождена или освобождена, и (2) освобождает память, выделенную стандартным new
оператором для объекта, когда он был построен. Это обратный порядок того, что происходит с new
оператором, который (1) выделяет память для объекта и выполняет базовую инициализацию, необходимую для создания строительной среды для объекта, и (2) вызывает конструктор объекта для создания объекта, начиная государство. См. Что делает новый оператор C++, кроме выделения и вызов ctor?
Таким образом, ключевым вопросом для вашего собственного дебетера является "какие действия, которые были сделаны до вызова конструктора объекта, нужно разматывать и отступать после завершения деструктора объекта?" ,
Обычно это какое-то распределение памяти, которое выполняется стандартным new
оператором.
Однако в случае некоторого ресурса, отличного от памяти, выделенного new
оператором, использование оператора delete
не подходит, поскольку ресурс не является памятью, которая была выделена с помощью new
оператора.
Поэтому при использовании интеллектуального указателя для такого рода ресурсов, где оператор delete
не подходит, вам необходимо предоставить свой собственный метод или функцию делетера, который интеллектуальный указатель будет использовать, когда он выходит из сферы действия, и запускает свой собственный деструктор, который будет в свою очередь, обрабатывают отбрасывание любых ресурсов, которыми управляет интеллектуальный указатель.
Простой пример с выходом
Я собрал простой пример с std::unique_ptr<>
вместе с выводом, сгенерированным для отображения с использованием и без использования деаэратора с указателем, а также с явным использованием деструктора.
Исходный код простого консольного приложения Windows выглядит так:
// ConsoleSmartPointer.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <memory>
#include <string>
#include <iostream>
class Fred {
public:
Fred() { std::cout << " Fred Constructor called." << std::endl; }
~Fred() { std::cout << " Fred Destructor called." << std::endl; }
};
class George {
public:
George() { std::cout << " George Constructor called" << std::endl; }
~George() { std::cout << " George Destructor called" << std::endl; }
private:
int iSomeData;
std::string a_label;
Fred myFred;
};
void cleanupGeorge(George *)
{
// just write out a log and do not explicitly call the object destructor.
std::cout << " cleanupGeorge() called" << std::endl;
}
void cleanupGeorge2(George *x)
{
// write out our message and then explicitly call the destructor for our
// object that we are the deleter for.
std::cout << " cleanupGeorge2() called" << std::endl;
x->~George(); // explicitly call destructor to do cleanup.
}
int func1()
{
// create a unique_ptr<> that does not have a deleter.
std::cout << "func1 start. No deleter." << std::endl;
std::unique_ptr<George> p(new George);
std::cout << "func1 end." << std::endl;
return 0;
}
int func2()
{
// create a unique_ptr<> with a deleter that will not explicitly call the destructor of the
// object created.
std::cout << "func2 start. Special deleter, no explicit destructor call." << std::endl;
std::unique_ptr<George, void(*)(George *)> p(new George, cleanupGeorge);
std::cout << "func2 end." << std::endl;
return 0;
}
int func3()
{
// create a unique_ptr<> with a deleter that will trigger the destructor of the
// object created.
std::cout << "func3 start. Special deleter, explicit destructor call in deleter." << std::endl;
std::unique_ptr<George, void(*)(George *)> p(new George, cleanupGeorge2);
std::cout << "func3 end." << std::endl;
return 0;
}
int main()
{
func1();
func2();
func3();
return 0;
}
Вышеприведенное простое приложение генерирует следующий результат:
func1 start. No deleter.
Fred Constructor called.
George Constructor called
func1 end.
George Destructor called
Fred Destructor called.
func2 start. Special deleter, no explicit destructor call.
Fred Constructor called.
George Constructor called
func2 end.
cleanupGeorge() called
func3 start. Special deleter, explicit destructor call in deleter.
Fred Constructor called.
George Constructor called
func3 end.
cleanupGeorge2() called
George Destructor called
Fred Destructor called.
Дополнительные сообщения
Что такое умный указатель, и когда я должен его использовать?
Использование пользовательского удаления с помощью std :: shared_ptr
См. Также эту дискуссию о deleter с std::make_shared<>
и почему она недоступна. Как передать deleter в make_shared?
Является ли пользовательским удалением для std :: unique_ptr допустимым местом для ручного вызова деструктора?
Когда std :: unique_ptr <A> нужен специальный дебетер, если A имеет деструктор?
RAII и интеллектуальные указатели в C++
Ответ 2
Когда (очевидно) delete
не так, как вы хотите уничтожить объект. Простым примером может быть объект, выделенный новым местом размещения.
Пример из праймера на самом деле довольно хорош (я должен их один после их раннего извлечения), но другое творческое использование std::shared_ptr
(или std::unique_ptr
) может заключаться в управлении временем жизни COM-объекта. Они освобождаются путем вызова метода Release()
, а не путем вызова delete
(и если вы это сделали, ну, в добрую ночь в Вене).
Поэтому, чтобы проиллюстрировать это, вы можете:
static void release_com_object (IUnknown *obj) { obj->Release (); }
IUnknown *my_com_object = ...
std::shared_ptr <IUnknown> managed_com_object (my_com_object, release_com_object);
Вам не нужно ничего знать о COM, чтобы понять основную идею здесь. Есть, в общем, любое количество способов выпуска ресурса и подходящий набор пользовательских удалений, может справиться со всеми, это действительно классный трюк.
Ах, я действительно сейчас попадаю в паз. Вот еще один для вас, на этот раз с std::unique_ptr
и лямбдой (не знаю, почему они используют shared_ptr
там - это намного дороже). Обратите внимание на различный синтаксис при использовании std::unique_ptr
- вы должны указать шаблону подпись функции делетера:
FILE *f = fopen ("myfile", "r");
if (f)
{
std::unique_ptr <FILE, void (*) (FILE *)> (f, [] (FILE *f) { fclose (f); });
// Do stuff with f
} // file will be closed here
О, мой, ты можешь многое сделать.
Демо-версия.
Ответ 3
В этом примере показано, как использовать детерминированное время жизни экземпляров типа. То, что происходит при их уничтожении, определяется деструктором (исключает встроенные типы, у них их нет). Деструктор является частью типа, который "очищает" его состояние. Хотя часто не так много делать, выделения памяти действительно нужно очистить, и в этом примере должна быть вызвана функция разъединения. Это справедливо для любого типа, который вручную управляет ресурсами (кроме простой агрегации или знакомых переменных-членов), и пример может быть одинаково хорошо
class ConnectionHandle {
public:
ConnectionHandle(destination& d) : c(connect(d)) {}
~ConnectionHandle() { end_connection(c); }
private:
connection& c;
};
Когда время жизни такого типа должно управляться с помощью умного указателя, это одна возможность использовать деструктор умного указателя для очистки ресурсов и того, что такое exapmle. Это работает для std::shared_ptr
а также std::unique_ptr
, хотя в последнем случае пользовательский делетер является частью сигнатуры типа (более типизируется при передаче unique_ptr
).
Поучительно сравнивать эту ситуацию с теми, где не требуется какой-либо пользовательский делектор:
struct A { int i; std::string str; };
auto sp = std::make_shared<A>(42, "foo");
Здесь ресурсы A
являются значениями, принадлежащими A
("агрегация"), очистка происходит автоматически (ничего не делать для i
, str
управляется std::string::~string()
).
Ответ 4
C++ позволяет вам создавать собственные пользовательские распределители с new
. Так же, как, как вы должны delete
все, что new
, вы должны иметь весь выделенную настраиваемый распределитель быть также удалены им.
Конкретным примером проблемы, вызванной этим, было бы, если вы используете пользовательский распределитель для отслеживания бюджетов памяти (то есть вы назначаете каждое выделение на какой-то бюджет и представляете предупреждение, когда превышаете любой из этих бюджетов). Скажем, это обертывает new
и delete
, поэтому, когда ваш умный указатель выходит из области видимости, вызывается только delete
, а пользовательский распределитель не имеет понятия, что память была освобождена, и вы закончите с неточным использованием памяти для своих бюджетов.
Если для обнаружения утечек использовался тот же тип распределителя обмотки, то вызов delete
напрямую может привести к ложному срабатыванию.
Если вы по-разному распределяете свою собственную память по любой причине, тогда у вас будет очень плохое время, когда delete
попытается ее освободить.
В случае вашего примера, память сетевого подключения освобождается, не будучи в состоянии полностью отключиться. Результатом этого в реальном случае может быть то, что другой конец соединения зависает до тех пор, пока он не истечет, или не представит какую-либо ошибку о сброшенном соединении.