Shared_ptr странность с нулевыми значениями и пользовательским удалением

Недавно мы столкнулись с сбоем при переходе от unique_ptr к shared_ptr с использованием пользовательского делетера. Авария произошла, когда указатель, используемый для создания умного указателя, был нулевым. Ниже приведен код, который воспроизводит проблему, и показывает два случая, которые работают.

В приведенном ниже источнике One и Two работают счастливо, а три аварии в "ReleaseDestroy". Сбой, похоже, происходит, когда класс, используемый в интеллектуальном указателе, имеет виртуальную "Release", поэтому программа пытается найти V-таблицу. unique_ptr выглядит так, как будто он проверяет нулевые указатели и не запускает деструктор. Похоже, что общий указатель пренебрегает этим.

Кто-нибудь знает, если это по дизайну, или это ошибка в реализации stl? Мы используем Visual Studio 2015.

#include <iostream>
#include <memory>

template<class R>
void ReleaseDestroy(R* r)
{
    r->Release();
};

class FlatDestroy
{
public :
    void Release()
    {
        delete this;
    }
};

class VirtualDestroy
{
public:
    virtual void Release()
    {
        delete this;
    }
};

class SimpleOne
{
public :
};

void main()
{
    std::shared_ptr<SimpleOne> One(nullptr);
    std::shared_ptr<FlatDestroy> Two(nullptr, ReleaseDestroy<FlatDestroy>);
    std::shared_ptr<VirtualDestroy> Three(nullptr, ReleaseDestroy<VirtualDestroy>);

    One.reset();
    Two.reset();
    Three.reset();
}

Ответ 1

Поведение разрушения unique_ptr и shared_ptr отличается:

  • unique_ptr только вызывает делеттер, если его удерживаемый указатель не равен нулю.

  • shared_ptr всегда вызывает дебетер.

Таким образом, ваш общий деинтер указателя должен иметь возможность иметь дело с значениями нулевого указателя! Например, std::free в порядке, но std::fclose нет, и ни один из них не является вашим делетером (так как это разыменовывает r безоговорочно).

Кстати, это появляется в LWG 2415, в котором рассматривается построение общего указателя с уникального указателя, который был разбит до этого разрешения дефекта именно по этой причине. (Я сам нахожу эту проблему , обратите внимание, как этот код тщательно различает нулевой регистр для shared_ptr.)