Почему инициализатор функции std::должен быть копируемым?

#c #copy-constructor #move-constructor #function-object

Вопрос:

Согласно http://en.cppreference.com/w/cpp/utility/functional/function/function, тип инициализатора, т. е. F в форме (5), должен соответствовать требованиям CopyConstructible. Я не совсем понимаю это. Почему это не нормально для F того, чтобы быть просто подвижным?

Ответ №1:

std::функция использует стирание типов внутри, поэтому F должен быть копируемым, даже если конкретный объект std::function, который вы используете, никогда не копируется.

Упрощение того, как работает стирание типов:

 class Function
{
    struct Concept {
        virtual ~Concept() = defau<
        virtual Concept* clone() const = 0;
        //...
    }

    template<typename F>
    struct Model final : Concept {

        explicit Model(F f) : data(std::move(f)) {}
        Model* clone() const override { return new Model(*this); }
        //...

        F data;
    };

    std::unique_ptr<Concept> object;

public:
    template<typename F>
    explicit Function(F f) : object(new Model<F>(std::move(f))) {}

    Function(Function constamp; that) : object(that.object->clone()) {}
    //...

};
 

Вы должны уметь генерировать Model<F>::clone() , что заставляет F быть копируемым.

Комментарии:

1. Итак, ключ кроется в virtual ключевом слове. Виртуальный метод всегда создается, даже если он является членом шаблона класса и фактически не используется. Напротив, невиртуальный метод шаблона класса создается только тогда, когда он фактически используется. Похоже, что если я уверен function<F> , что не буду скопирован, тогда безопасно реализовать конструктор копирования F как фиктивный, так как он никогда не будет вызван на самом деле.

2. @Lingxi: Тщательно документируйте это, и у вас есть утверждения и исключения.

Ответ №2:

Пример из @Nevin показателен с точки зрения демонстрации варианта реализации. Тем не менее, здесь действует нечто более фундаментальное. Это не является артефактом конкретной используемой техники реализации.

В частности, на самом virtual деле это не является ключевым моментом здесь. Рассмотрим эту альтернативную реализацию, которая не используется virtual (кроме как в деструкторе).

 class Function
{
  struct Concept {
    typedef Concept * (*cloneFunType)(const Concept *);
    cloneFunType m_cloneFun = nullptr;    
    virtual ~Concept() = defau<
  };

  template<typename F> struct Model final : Concept {
    static Concept* clone(const Concept *c)  { 
      return new Model(static_cast<const Model*>(c)->data); }

    explicit Model(F amp;amp;f) : data(move(f)) { this->m_cloneFun = amp;Model::clone;}

    explicit Model(const F amp;f) : data(f) { this->m_cloneFun = amp;Model::clone; }

    F data;
  };

  Concept* object;

public:
  ~Function() { delete object; }

  template<typename F> explicit Function(Famp;amp; f) 
    : object(new Model<typename remove_reference<F>::type>(forward<F>(f))) {} 

  Function(Function constamp; that) 
    : object((*(that.object->m_cloneFun))(that.object)) {}

  Function(Function amp;amp; that) : object(that.object) { that.object = nullptr; }
    //...
};
 

видишь http://ideone.com/FKFktK для полной версии и вывода примера

Рассмотрим, каково значение выражения (также в http://ideone.com/FKFktK):

is_copy_constructible<function<void()>>::value

Ответ не может зависеть от свойств конкретных экземпляров или от того, как они были созданы, в этом случае даже нет экземпляра, на который можно было бы посмотреть. Копируемость-это свойство типа, а не экземпляра. Таким образом, ответ должен быть одинаковым true или false во всех случаях.

Стандарт решил is_copy_constructible<function<void()>>::value быть true таким . Как следствие, стандарт вынужден требовать, чтобы это is_copy_constructible<F>::value также было true независимо от внутренних компонентов реализации шаблона функции std::.

Если бы мы выбрали is_copy_constructible<function<void()>>::value это false , то ни один экземпляр не был бы копируемым, независимо от того, был ли какой-то конкретный F экземпляр копируемым.

Ответ №3:

std::function является копируемым (см. Конструктор (3) в документации). Вы можете скопировать объект только в том случае, если все его компоненты могут быть скопированы. Таким образом, содержащееся F также должно быть копируемым. Вот так просто.

Комментарии:

1. Значит, если бы этот конструктор был удален, проблема исчезла бы так просто? 🙂