#c #optimization #design-patterns #smart-pointers
Вопрос:
Это пример интеллектуального указателя с удаленным удалителем типов и включенной оптимизацией локального буфера. Это из книги https://books.google.am/books/about/Hands_On_Design_Patterns_with_C .html?id=iQGGDwAAQBAJamp;printsec=frontcoveramp;source=kp_read_buttonamp;redir_esc=y#v=onepageamp;qamp;f=false
#include <iostream>
template <typename T>
class smart_ptr
{
struct deleter_base
{
virtual ~deleter_base() {};
virtual void apply(T* p) = 0;
};
template <typename Deleter>
struct deleter : public deleter_base
{
deleter(Deleter d): m_d(d) {}
void apply(T* p) override
{
m_d(p);
}
Deleter m_d;
};
deleter_base* m_db;
T* m_p;
char m_buf[16];
public:
template <typename Deleter>
smart_ptr(T* p, Deleter d)
: m_p(p)
, m_db( sizeof(Deleter) > sizeof(m_buf) ? new deleter<Deleter>(d) : new (m_buf) deleter<Deleter>(d))
{
}
~smart_ptr()
{
m_db->apply(m_p);
if (static_cast<void*>(m_db) == static_cast<void*>(m_buf))
{
m_db->~deleter_base();
}
else
{
delete m_db;
}
}
Tamp; operator*() {return *m_p;}
const Tamp; operator*() const {return *m_p;}
T* operator->() { return m_p; }
const T* operator->() const { return m_p; }
};
struct Test
{
Test(double d): m_d(d) { std::cout << "Test::Test" << std::endl; }
~Test() { std::cout << "Test::~Test" << std::endl; }
double m_d;
};
int main()
{
auto deleter = [](Test * t) {delete t;};
smart_ptr<Test> spd(new Test(3.6), deleter);
std::cout << spd->m_d << std::endl;
}
Один вопрос. Когда удалитель не помещается в локальный буфер, мы выделяем для него память и оставляем m_buf
неинициализированным. Это ошибка или я что-то пропустил?
В деструкторе мы сравниваем m_buf
, какое значение имеет случайное значение, со значением, возвращаемым new deleter<Deleter>(d)
. Не исключено, что эти значения совпадают, верно?
Комментарии:
1.
m_buf
обеспечивает хранение (используется с размещением нового). До тех пор, пока реализация всегда полагается наm_db
ссылку на удалитель, независимо от того, является лиm_buf
он неинициализированным, если он не используется. Вы можете войти. с{ }
тем, чтобы сделать это более надежным, но это можно рассматривать как пессимизацию.2. Ошибка-это что-то (не) работающее неправильно. Как содержимое буфера «неправильно», если мы его не используем?
3. Но мы используем его внутри деструктора smart_ptr. Мы читаем там из неинициализированной памяти, нет ?
4. Мы сравниваем адрес буфера с чем-то. До этого мы не получаем доступ к его содержимому.
5. …но с явной проверкой перед:
if (static_cast<void*>(m_db) == static_cast<void*>(m_buf))
что защищает случай, когда он не является неинициализированным.
Ответ №1:
Это не ошибка — допустимо иметь неинициализированную память. У вас есть неопределенное поведение (UB) только при чтении из неинициализированной памяти.
Это имеет смысл — если просто наличие неинициализированной памяти уже было бы ошибкой, то размещение нового, например, в smart_ptr
описании выше, было бы бессмысленным. Но это безопасно, потому что все начинается с записи.
Комментарии:
1. m_buf будет содержать случайное значение, что делать, если случайно содержимое m_db совпадает с этим случайным значением. Разве это не невозможно, верно?
2.@Ashot: Он действительно будет содержать случайное значение, если не используется для
Deleter
. Ноm_buf
адрес не будет случайным. Это будет немного больше, чемthis
из-за предыдущегоm_db
иm_p
.3.
m_buf
адрес не имеет отношения к делу. Мы не сравниваем адресm_buf
, но это ценность. Он сравнивается со значением, возвращаемымnew deleter<Deleter>(d)
. Я не вижу причины, по которой эти ценности всегда должны быть разными.4. @Ашот: Это неправильно.
m_buf
используется какstatic_cast<void*>(m_buf)
. Теперьm_buf
это массив, поэтому это приведение вызывает неявное преобразование изchar[16]
вchar*
, а затем явное преобразование изchar*
вvoid*
. Этоchar*
адресm_buf[0]
начала массива.