Должен ли я скопировать std :: function или я всегда могу взять ссылку на нее? - PullRequest
56 голосов
/ 03 января 2012

В моем приложении C ++ (использующем 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 - это просто функцияуказатель и «исполняемый код» функции гарантированно останется в памяти?
  • Нужно ли делать копию в случае передачи лямбды и возврата вызывающего абонента?* Мои интуитивные ощущения говорят, что хранить ссылку (даже const-ссылку) безопасно.Я ожидаю, что компилятор сгенерирует код для лямбды во время компиляции и сохранит этот исполняемый код в «виртуальной» памяти во время работы приложения.Поэтому исполняемый код никогда не удаляется, и я могу смело хранить ссылку на него.Но так ли это на самом деле?

Ответы [ 5 ]

54 голосов
/ 03 января 2012

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

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

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

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

14 голосов
/ 19 апреля 2013

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

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

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

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

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

private:
   MyFunction m_myFunction;
};

Таким образом, если они передают значение, указанное выше, компилятор оптимизирует первую копию в конструкторе, а 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;
};

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

Соответствующая ссылка (и, вероятно, довольно неплохо здесь) (* очень хорошее чтение): http://cpp -next.com/ Архив / 2009/08 / хотите-скорость-передача по значению /

3 голосов
/ 26 августа 2013

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

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

2 голосов
/ 03 января 2012

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

MyFunction m_myFunction; //prefferd and safe!

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

2 голосов
/ 03 января 2012

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

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

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.

...