Подтвердить что ты не робот

С++ std:: unique_ptr: Почему нет платы за размер с лямбдами?

Я читаю "Эффективный современный С++". В элементе, относящемся к std::unique_ptr, было указано, что если пользовательский делектор является объектом без гражданства, тогда плата за размер не возникает, но если это указатель на функцию или размер ставки t21. Не могли бы вы объяснить, почему?

Скажем, что у нас есть следующий код:

auto deleter_ = [](int *p) { doSth(p); delete p; };
std::unique_ptr<int, decltype(deleter_)> up(new int, deleter_);

Насколько я понимаю, unique_ptr должен иметь объект типа decltype(deleter_) и назначить deleter_ этому внутреннему объекту. Но, очевидно, это не то, что происходит. Не могли бы вы объяснить механизм этого, используя наименьший возможный пример кода?

4b9b3361

Ответ 1

A unique_ptr должен всегда хранить свой дебит. Теперь, если делектор - это тип класса без состояния, то unique_ptr может использовать пустую базовую оптимизацию, чтобы деблокировать не используйте дополнительное пространство.

Как именно это делается, различается между реализациями. Например, libС++ и MSVC хранят управляемый указатель и удаляющий элемент в сжатая пара, которая автоматически получает пустую оптимизацию базы, если один из типов - пустой класс.

Из ссылки libС++ выше

template <class _Tp, class _Dp = default_delete<_Tp> >
class _LIBCPP_TYPE_VIS_ONLY unique_ptr
{
public:
    typedef _Tp element_type;
    typedef _Dp deleter_type;
    typedef typename __pointer_type<_Tp, deleter_type>::type pointer;
private:
    __compressed_pair<pointer, deleter_type> __ptr_;

libstdС++ хранит два в std::tuple, а некоторые поиски Google показывают, что их реализация tuple использует пустую оптимизацию базы, но я могу" t найти какую-либо документацию, указывающую так явно.

В любом случае в этом примере демонстрирует, что и libС++, и libstdС++ используют EBO для уменьшения размера unique_ptr с пустым удалением.

Ответ 2

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

Возможно, понимание Empty Base Optimization поможет вам понять, как это можно реализовать на практике.
std::is_empty - это еще одна возможность того, как это можно реализовать.

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

Ответ 3

Из реализации unique_ptr:

template<class _ElementT, class _DeleterT = std::default_delete<_ElementT>>
class unique_ptr
{
public:
   // public interface...

private:

  // using empty base class optimization to save space
  // making unique_ptr with default_delete the same size as pointer

  class _UniquePtrImpl : private deleter_type
  {
  public:
     constexpr _UniquePtrImpl() noexcept = default;

     // some other constructors...

     deleter_type& _Deleter() noexcept
     { return *this; }

     const deleter_type& _Deleter() const noexcept
     { return *this; }

     pointer& _Ptr() noexcept
     { return _MyPtr; }

     const pointer _Ptr() const noexcept
     { return _MyPtr; }

  private:
     pointer   _MyPtr;

  };

  _UniquePtrImpl   _MyImpl;

};

Класс _UniquePtrImpl содержит указатель и выводится из deleter_type.

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

Ответ 4

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

Но для несоблюдения лямбда есть два ключевых факта:

  • Тип лямбда уникален и известен только компилятору.
  • Нехватывающие лямбды не имеют гражданства.

Следовательно, компилятор может вызывать лямбду исключительно на основе своего типа, который записывается как часть типа unique_ptr; никакая дополнительная информация о времени выполнения не требуется.

На самом деле, почему несобранные лямбды не имеют гражданства. Что касается вопроса о размерном штрафе, то, конечно, ничего особенного в том, что я не умею снимать lambdas по сравнению с любым другим функтором-функтором делегирования без имени.

Обратите внимание, что std::function не является апатридом, поэтому к нему не применяются те же рассуждения.

Наконец, обратите внимание на то, что, хотя для объектов с отсутствующим статусом обычно требуется ненулевой размер, чтобы гарантировать, что у них есть уникальные адреса, базовые классы без состояния не обязаны добавлять к общему размеру производного типа; это называется пустой базой оптимизации. Таким образом, unique_ptr может быть реализовано (как и в ответе Бо Персона) как тип, который происходит от типа делетера, который, если он без гражданства, не будет вносить штраф за размер. (Фактически это может быть единственным способом правильно реализовать unique_ptr без штрафа размера для безстоящих лиц, но я не уверен.)