Должен ли я копировать функцию std:: или я могу всегда ссылаться на нее?

В моем приложении на С++ (с использованием Visual Studio 2010) мне нужно сохранить std:: function, например:

class MyClass
   {
   public:
      typedef std::function<int(int)> MyFunction;
      MyClass (Myfunction &myFunction);
   private:
      MyFunction m_myFunction;    // Should I use this one?
      MyFunction &m_myFunction;   // Or should I use this one?
   };

Как вы можете видеть, я добавил аргумент функции в качестве ссылки в конструкторе.

Но каков наилучший способ сохранить функцию в моем классе?

  • Могу ли я сохранить функцию в качестве ссылки, поскольку std:: function - это просто указатель на функцию, и "исполняемый код" функции гарантированно останется в памяти?
  • Должен ли я сделать копию в случае передачи лямбда и возврата вызывающего?

Чувство кишки говорит, что безопасно хранить ссылку (даже константу-ссылку). Я ожидаю, что компилятор будет генерировать код для лямбда во время компиляции и сохранить этот исполняемый код в виртуальной памяти во время работы приложения. Поэтому исполняемый код никогда не "удаляется", и я могу безопасно хранить ссылку на него. Но действительно ли это так?

Ответ 1

Могу ли я сохранить функцию в качестве ссылки, поскольку std:: function - это просто указатель на функцию, и "исполняемый код" функции гарантированно останется в памяти?

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

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

Передача const ссылки на конструктор безопасна и, вероятно, более эффективна, чем передача значения. Передача с помощью ссылки не const - это плохая идея, поскольку она не позволяет вам передавать временную информацию, поэтому пользователь не может напрямую передать лямбда, результат bind или любой другой вызываемый объект, кроме std::function<int(int)>.

Ответ 2

Если вы передаете функцию in в конструктор по ссылке и не делаете ее копию, вам не повезет, когда функция выходит за рамки вне этого объекта, так как ссылка больше не будет действительны. Об этом уже говорилось в предыдущих ответах.

Я хотел добавить, что вместо этого вы могли бы передать функцию значение, а не ссылку, в конструктор. Зачем? ну, вам все равно нужна копия, поэтому, если вы пройдете по значению, компилятор может оптимизировать необходимость создания копии при передаче временного кода (например, выражение лямбда, написанное на месте).

Конечно, как бы вы ни делали, вы можете сделать другую копию, когда назначаете переданную функцию переменной, поэтому используйте std::move для устранения этой копии. Пример:

class MyClass
{
public:
   typedef std::function<int(int)> MyFunction;

   MyClass (Myfunction myFunction): m_myfunction(std::move(myFunction))
       {}

private:
   MyFunction m_myFunction;
};

Итак, если они передают значение r выше, компилятор оптимизирует первую копию в конструкторе, а std:: move удаляет второй:)

Если ваш (единственный) конструктор принимает константу-ссылку, вам будет нужно сделать копию ее в функции независимо от того, как она прошла.

Альтернативой является определение двух конструкторов, связанных с lvalues ​​и rvalues ​​отдельно:

class MyClass
{
public:
   typedef std::function<int(int)> MyFunction;

   //takes lvalue and copy constructs to local var:
   MyClass (const Myfunction & myFunction): m_myfunction(myFunction)
       {}
   //takes rvalue and move constructs local var:
   MyClass (MyFunction && myFunction): m_myFunction(std::move(myFunction))
       {}

private:
   MyFunction m_myFunction;
};

Теперь вы ручаете rvalues ​​по-разному и исключаете необходимость копировать в этом случае, явно обрабатывая его (а не позволяя компилятору обрабатывать его для вас). Может быть немного более эффективным, чем первый, но также больше кода.

(видимо, здесь можно увидеть справедливую битку) соответствующую ссылку (и очень хорошее чтение): http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

Ответ 3

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

Еще одно соображение - любые скрытые переменные состояния в std:: function, для которых модификация вряд ли будет потокобезопасной. Это означает, что даже если вызов базовой функции является потокобезопасным, оболочка std:: function "()" вызывает его НЕ МОЖЕТ БЫТЬ. Вы можете восстановить желаемое поведение, всегда используя потоковые локальные копии функции std::, потому что каждая из них будет иметь изолированную копию переменных состояния.

Ответ 4

Скопируйте столько, сколько хотите. Он можно копировать. Большинство алгоритмов в стандартной библиотеке требуют, чтобы функторы были.

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

class MyClass
{
public:
    typedef std::function<int(int)> MyFunction;
    MyClass (const Myfunction &myFunction);
          // ^^^^^ pass by CONSTANT reference.
private:
    MyFunction m_myFunction;    // Always store by value
};

Проходя по ссылке константы или rvalue, вы обещаете вызывающему абоненту, что вы не будете изменять эту функцию, пока вы ее еще можете вызвать. Это не позволяет вам модифицировать функцию по ошибке, и ее обычно следует избегать, так как она менее читаема, чем использование возвращаемого значения.

Изменить: Я изначально сказал "CONSTANT или rvalue" выше, но комментарий Дэйва заставил меня посмотреть его, и действительно ссылка rvalue не принимает lvalues.

Ответ 5

Я предлагаю вам сделать копию:

MyFunction m_myFunction; //prefferd and safe!

Это безопасно, потому что если исходный объект выходит из области, разрушающей себя, копия все еще будет существовать в экземпляре класса.