Явно инициализировать указатель функции [duplicate]

В то время как , что вызывает NullReferenceExceptions и подходит к avoid / fix , такое исключение было рассмотрено в других ответах, что многие программисты не имеют " t узнал еще, как независимо отлаживать такие исключения во время разработки.

В Visual Studio это обычно легко благодаря Visual Studio Debugger .


Во-первых, убедитесь, что правильная ошибка будет обнаружена - см. . Как разрешить нарушение «Исключение System.NullReferenceException» в VS2010? Примечание1

Затем либо Начать с отладки (F5) , либо Приложить [отладчик VS] к запуску процесса . Иногда может быть полезно использовать Debugger.Break , в котором будет предложено запустить отладчик.

Теперь, когда NullReferenceException выбрано (или необработанно), отладчик остановится ( помните правило, указанное выше?) в строке, на которой произошло исключение. Иногда ошибка может быть легко обнаружена.

Например, в следующей строке единственный код, который может , вызывает исключение, если myString имеет значение null. Это можно проверить, посмотрев окно Watch или выполнив выражения в окне Immediate Window .

var x = myString.Trim();

В более сложных случаях, таких как следуя ниже, вам нужно будет использовать один из методов выше (Watch или Immediate Windows) для проверки выражений, чтобы определить, было ли str1 пустым или если str2 имеет значение null.

var x = str1.Trim() + str2.Trim();

Once , где было выбрано исключение, это обычно тривиально по отношению к разуму назад, чтобы выяснить, где введенное значение null было [неправильно] -

. Найдите время, необходимое для понимания причина исключения. Проверьте нулевые выражения. Проверьте предыдущие выражения, которые могли бы привести к таким нулевым выражениям. Добавьте контрольные точки и, по мере необходимости, пройдите через программу. Используйте отладчик.


1 Если Break on Throws слишком агрессивен и отладчик останавливается на NPE в библиотеке .NET или сторонних разработчиков, Break на User-Unhandled можно использовать для ограничения выловленных исключений. Кроме того, VS2012 представляет Just My Code , который я рекомендую также включить.

Если вы отлаживаете с включенным Just My Code, поведение немного отличается. При включенном Just My Code отладчик игнорирует исключения, связанные с привилегиями обычного языка (CLR) первого шанса, которые выходят за пределы My Code и не проходят через My Code

blockquote>

1202
задан hugomg 2 November 2011 в 23:12
поделиться

8 ответов

Проблема

C ++ включает полезные общие функции, такие как std::for_each и std::transform, что может быть очень удобно. К сожалению, они также могут быть довольно громоздкими в использовании, особенно если functor , который вы хотите применить, уникален для конкретной функции.

#include <algorithm>
#include <vector>

namespace {
  struct f {
    void operator()(int) {
      // do something
    }
  };
}

void func(std::vector<int>& v) {
  f f;
  std::for_each(v.begin(), v.end(), f);
}

Если вы используете только f один раз и в этом конкретном месте кажется излишним писать целый класс, просто чтобы сделать что-то тривиальное и одно.

В C ++ 03 у вас может возникнуть соблазн написать что-то вроде следующего, чтобы сохранить functor local:

void func2(std::vector<int>& v) {
  struct {
    void operator()(int) {
       // do something
    }
  } f;
  std::for_each(v.begin(), v.end(), f);
}

однако это недопустимо, f не может быть передано функции template в C ++ 03.

новое решение

C ++ 11 вводит lambdas, чтобы вы могли написать встроенный анонимный функтор для замены struct f. Для небольших простых примеров это может быть более чистым для чтения (он хранит все в одном месте) и потенциально проще поддерживать, например, в простейшей форме:

void func3(std::vector<int>& v) {
  std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}

Лямбда-функции - это просто синтаксический сахар для анонимных функторов .

Типы возвращаемых данных

В простых случаях для вас выводится тип возврата лямбда, например:

void func4(std::vector<double>& v) {
  std::transform(v.begin(), v.end(), v.begin(),
                 [](double d) { return d < 0.00001 ? 0 : d; }
                 );
}

, однако, когда вы начинаете писать больше сложный lambdas, вы быстро столкнетесь с случаями, когда тип возврата не может быть выведен компилятором, например:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

Чтобы разрешить это, вам разрешено явно указывать тип возврата для лямбда-функции, используя -> T:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) -> double {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

«Захват» переменных

До сих пор мы не использовали ничего, кроме того, что было передано лямбда внутри него, но мы также можем использовать другие переменные, в пределах лямбда. Если вы хотите получить доступ к другим переменным, вы можете использовать предложение capture ([] выражения), которое до сих пор не использовалось в этих примерах, например:

void func5(std::vector<double>& v, const double& epsilon) {
    std::transform(v.begin(), v.end(), v.begin(),
        [epsilon](double d) -> double {
            if (d < epsilon) {
                return 0;
            } else {
                return d;
            }
        });
}

Вы можете выполнять захват обоими ссылку и значение, которые вы можете указать с помощью & и = соответственно:

  • [&epsilon] захват по ссылке
  • [&] захватывает все переменные, используемые в lambda по ссылке
  • [=] фиксирует все переменные, используемые в лямбда по значению
  • [&, epsilon] захватывает переменные, такие как [& amp;], но epsilon по значению
  • [=, &epsilon] захватывает переменные, такие как [=], но epsilon по ссылке

Порожденный operator() по умолчанию const, с импликацией, которая захватывает, будет const когда вы обращаетесь к ним по умолчанию. Это приводит к тому, что каждый вызов с одним и тем же входом даст тот же результат, однако вы можете пометить лямбда как mutable , чтобы запросить, что созданный operator() не const.

1213
ответ дан Ahmed 27 August 2018 в 00:00
поделиться

Одна проблема, которую он решает: Код проще, чем lambda для вызова в конструкторе, который использует функцию выходного параметра для инициализации члена const

Вы можете инициализировать член const вашего класс, с вызовом функции, которая устанавливает его значение, возвращая свой вывод в качестве выходного параметра.

1
ответ дан Community 27 August 2018 в 00:00
поделиться

Одно из лучших объяснений lambda expression дано автором C ++ Bjarne Stroustrup в его книге ***The C++ Programming Language*** глава 11 ( ISBN-13: 978-0321563842 ):

What is a lambda expression?

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

When would I use one?

< blockquote>

Это особенно полезно, когда мы хотим передать операцию в качестве аргумента алгоритму. В контексте графических пользовательских интерфейсов (и в других местах) такие операции часто называют обратными вызовами .

What class of problem do they solve that wasn't possible prior to their introduction?

Здесь Я предполагаю, что каждое действие, выполняемое с помощью лямбда-выражения, может быть разрешено без них, но с гораздо большим количеством кода и гораздо большей сложностью. Лямбда-выражение - это способ оптимизации вашего кода и способ сделать его более привлекательным. Как печально Stroustup:

эффективные способы оптимизации

Some examples

через lambda выражение

void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0
{
    for_each(begin(v),end(v),
        [&os,m](int x) { 
           if (x%m==0) os << x << '\n';
         });
}

или через функцию

class Modulo_print {
         ostream& os; // members to hold the capture list int m;
     public:
         Modulo_print(ostream& s, int mm) :os(s), m(mm) {} 
         void operator()(int x) const
           { 
             if (x%m==0) os << x << '\n'; 
           }
};

или даже

void print_modulo(const vector<int>& v, ostream& os, int m) 
     // output v[i] to os if v[i]%m==0
{
    class Modulo_print {
        ostream& os; // members to hold the capture list
        int m; 
        public:
           Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
           void operator()(int x) const
           { 
               if (x%m==0) os << x << '\n';
           }
     };
     for_each(begin(v),end(v),Modulo_print{os,m}); 
}

, если вам нужно u, можно назвать lambda expression, как показано ниже:

void print_modulo(const vector<int>& v, ostream& os, int m)
    // output v[i] to os if v[i]%m==0
{
      auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; };
      for_each(begin(v),end(v),Modulo_print);
 }

Или предположим, что еще один простой образец

void TestFunctions::simpleLambda() {
    bool sensitive = true;
    std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});

    sort(v.begin(),v.end(),
         [sensitive](int x, int y) {
             printf("\n%i\n",  x < y);
             return sensitive ? x < y : abs(x) < abs(y);
         });


    printf("sorted");
    for_each(v.begin(), v.end(),
             [](int x) {
                 printf("x - %i;", x);
             }
             );
}

будет генерировать следующий

0

1

0

1

0

1

0

1

0

1

0 sortedx - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;

[] - это список захвата или lambda introducer: если lambdas не требует доступа к своей локальной среде, мы можем его использовать.

Цитата из книги:

Первая Характер лямбда-выражения всегда [. Интенсификатор лямбда может принимать различные формы:

• []: пустой список захвата. Это означает, что никакие локальные имена из окружающего контекста не могут использоваться в лямбда-теле. Для таких лямбда-выражений данные получены из аргументов или из нелокальных переменных.

• [& amp;]: неявно захватывать по ссылке. Можно использовать все локальные имена. Доступ ко всем локальным переменным осуществляется по ссылке.

• [=]: неявно фиксировать по значению. Можно использовать все локальные имена. Все имена относятся к копиям локальных переменных, взятых в точке вызова лямбда-выражения.

• [capture-list]: явный захват; Список захвата - это список имен локальных переменных, которые должны быть захвачены (т. е. сохранены в объекте) по ссылке или по значению. Переменные с именами, которым предшествует & amp; записываются по ссылке. Другие переменные фиксируются по значению. Список захвата также может содержать это и имена, за которыми следуют ... как элементы.

• [& amp ;, capture-list]: неявно захватывать по ссылке все локальные переменные с именами, не указанными в списке. Этот список может содержать список захвата. Перечисленным именам не может предшествовать & amp ;. Переменные, названные в списке захвата, записываются по значению.

• [=, capture-list]: неявно захватывать по значению все локальные переменные с именами, не указанными в списке. Список захвата не может содержать этого. Перечисленным именам должно предшествовать & amp ;. Вариаторы, названные в списке захвата, записываются по ссылке.

Обратите внимание, что локальное имя, которому предшествует & amp; всегда фиксируется по ссылке и локальное имя, не указанное с помощью & amp; всегда фиксируется по значению.

Additional

Lambda expression format

Дополнительные ссылки:

6
ответ дан gbk 27 August 2018 в 00:00
поделиться

Ответы

Q: Что такое лямбда-выражение в C ++ 11?

A: Под капотом это объект автогенерированного класса с оператором перегрузки () Уст. Такой объект называется замыканием и создается компилятором. Эта концепция «закрытия» находится рядом с концепцией связывания с C ++ 11. Но lambdas обычно генерируют лучший код.

В: Когда я буду использовать один?

A: Определить «простую и малую логику» и спросить компилятор выполнить генерацию из предыдущего вопроса. Вы даете компилятору некоторые выражения, которые вы хотите быть внутри оператора ().

Q: Какой класс проблемы они решают, что было невозможно до их введения?

A: Это какой-то синтаксис сахара, как перегрузка операторов вместо функций для пользовательских операций add, subrtact ... Но он сохраняет больше строк ненужного кода, чтобы обернуть 1-3 строки реальной логики для некоторых классов и т. д.! Некоторые инженеры считают, что если число строк меньше, то вероятность ошибок в нем меньше (я тоже так думаю)

Пример использования

auto x = [=](int arg1){printf("%i", arg1); };
void(*f)(int) = x;
f(1);
x(1);

Дополнительные сведения о лямбдах, не затрагиваемые вопросом. Игнорируйте этот раздел, если вы не заинтересованы

1. Захваченные значения. Что вы можете захватить

1.1. Вы можете ссылаться на переменную со статической продолжительностью хранения в lambdas. Все они захвачены.

1.2. Вы можете использовать лямбда для значений захвата «по значению». В этом случае захваченные вары будут скопированы в объект функции (замыкание).

[captureVar1,captureVar2](int arg1){}

1.3. Вы можете захватить ссылку. & Амп; - в этом контексте означают ссылки, а не указатели.

   [&captureVar1,&captureVar2](int arg1){}

1.4. Он содержит обозначения для захвата всех нестатических варов по значению или по ссылке

  [=](int arg1){} // capture all not-static vars by value

  [&](int arg1){} // capture all not-static vars by reference

1.5. Он содержит обозначения для захвата всех нестатических варов по значению или путем ссылки и указания smth. Больше. Примеры: Захват всех нестатических варов по значению, но путем захвата ссылки Param2

[=,&Param2](int arg1){} 

Захват всех нестатических варов по ссылке, но с помощью захвата значения Param2

[&,Param2](int arg1){} 

2. Вывод типа возврата

2.1. Возвращаемый тип Lambda может быть выведен, если лямбда - это одно выражение. Или вы можете явно указать его.

[=](int arg1)->trailing_return_type{return trailing_return_type();}

Если lambda имеет более одного выражения, тогда возвращаемый тип должен быть указан с помощью возвращаемого типа возврата. Кроме того, подобный синтаксис может применяться к автофункциям и функциям-членам

3. Захваченные значения. Что вы не можете захватить

3.1. Вы можете захватывать только локальные вары, а не переменную-член объекта.

4. Свержения

4.1. lambda не является указателем на функцию и не является анонимной функцией, но может быть неявно преобразован в указатель на функцию.

p.s.

  1. Подробнее о лямбда-грамматической информации можно найти в рабочем проекте для языка программирования C ++ # 337, 2012-01-16, 5.1.2. Лямбда-выражения, стр.88
  2. В C ++ 14 добавлена ​​дополнительная функция, названная «захват init». Он позволяет выполнять произвольное объявление членов данных закрытия:
    auto toFloat = [](int value) { return float(value);};
    auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};
    
30
ответ дан KIN 27 August 2018 в 00:00
поделиться

Ну, одно практическое использование, которое я обнаружил, - это уменьшение кодовой таблички. Например:

void process_z_vec(vector<int>& vec)
{
  auto print_2d = [](const vector<int>& board, int bsize)
  {
    for(int i = 0; i<bsize; i++)
    {
      for(int j=0; j<bsize; j++)
      {
        cout << board[bsize*i+j] << " ";
      }
      cout << "\n";
    }
  };
  // Do sth with the vec.
  print_2d(vec,x_size);
  // Do sth else with the vec.
  print_2d(vec,y_size);
  //... 
}

Без lambda вам может понадобиться сделать что-то для разных случаев bsize. Конечно, вы могли бы создать функцию, но что, если вы хотите ограничить использование в рамках функции пользователя души? характер lambda выполняет это требование, и я использую его для этого случая.

1
ответ дан Klik 27 August 2018 в 00:00
поделиться

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

[&](){ ...your code... }(); // immediately executed lambda expression

функционально эквивалентен

{ ...your code... } // simple code block

. Это делает лямбда-выражения мощным инструментом для реорганизации сложных функций. Вы начинаете с упаковки раздела кода в лямбда-функции, как показано выше. Затем процесс явной параметризации можно выполнить постепенно с промежуточным тестированием после каждого шага. После полной настройки кодового блока (как показано в результате удаления &), вы можете переместить код во внешнее местоположение и сделать его нормальной функцией.

Аналогичным образом вы можете использовать лямбда-выражения для инициализации переменных на основе результата алгоритма ...

int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5!

Как способ разбиения вашей логики на программу, вы можете даже счесть полезным передать лямбда-выражение в качестве аргумента другому lambda expression ...

[&]( std::function<void()> algorithm ) // wrapper section
   {
   ...your wrapper code...
   algorithm();
   ...your wrapper code...
   }
([&]() // algorithm section
   {
   ...your algorithm code...
   });

Лямбда-выражения также позволяют создавать именованные вложенные функции , что может быть удобным способом избежать дублирования логики. Использование именованных lambdas также имеет тенденцию быть немного легче на глазах (по сравнению с анонимными встроенными лямбдами) при передаче нетривиальной функции в качестве параметра другой функции. Примечание: не забывайте точку с запятой после закрывающей фигурной скобки.

auto algorithm = [&]( double x, double m, double b ) -> double
   {
   return m*x+b;
   };

int a=algorithm(1,2,3), b=algorithm(4,5,6);

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

152
ответ дан NightFurry 27 August 2018 в 00:00
поделиться

Что такое лямбда-функция?

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

В C ++ функция лямбда определена следующим образом

[]() { } // barebone lambda

или во всей его славе

[]() mutable -> T { } // T is the return type, still lacking throw()

[] - это список захвата, () список аргументов и {} тело функции.

Список захвата

Список захвата определяет, что из-за лямбда должно быть доступно внутри тела функции и как. Это может быть либо:

  1. значение: [x]
  2. ссылка [& amp; x]
  3. любая переменная, находящаяся в настоящее время в области по ссылке [ & amp;]
  4. то же, что и 3, но по значению [=]

Вы можете смешать любое из вышеперечисленного в списке с разделителями-запятыми [x, &y].

Список аргументов

Список аргументов тот же, что и в любой другой функции C ++.

Тело функции

Код, который будет выполняться, когда на самом деле вызывается лямбда.

Вывод типа возврата

Если лямбда имеет только один оператор return, возвращаемый тип может быть опущен и имеет неявный тип decltype(return_statement).

Mutable

Если лямбда отмечена как изменяемая (например, []() mutable { }), она разрешено изменять значения, которые были записаны по значению.

Случаи использования

Библиотека, определенная стандартом ISO, в значительной степени зависит от лямбда и повышает удобство использования нескольких баров, так как теперь пользователи

C ++ 14

В C ++ 14 lambdas были расширены различными предложениями.

Инициализированные лямбда-захваты

Элемент списка захвата теперь можно инициализировать с помощью =. Это позволяет переименовывать переменные и захватывать, перемещаясь. Пример, взятый из стандарта:

int x = 4;
auto y = [&r = x, x = x+1]()->int {
            r += 2;
            return x+2;
         }();  // Updates ::x to 6, and initializes y to 7.

и один из Википедии, показывающий, как захватить с помощью std::move:

auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};

Generic Lambdas

Теперь Lambdas может быть общим (auto был бы эквивалентен T здесь, если T был аргументом шаблона типа где-то в окружающей области):

auto lambda = [](auto x, auto y) {return x + y;};

Улучшенная вычитание возвращаемого типа

C ++ 14 позволяет выводить возвращаемые типы для каждой функции и не ограничивает ее функциями return expression;. Это также распространяется на лямбда.

721
ответ дан pmr 27 August 2018 в 00:00
поделиться

Лямбда-функция - анонимная функция, которую вы создаете в строке. Он может захватывать переменные, как объясняли некоторые (например, http://www.stroustrup.com/C++11FAQ.html#lambda ), но есть некоторые ограничения. Например, если есть интерфейс обратного вызова, подобный этому,

void apply(void (*f)(int)) {
    f(10);
    f(20);
    f(30);
}

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

int col=0;
void output() {
    apply([](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

Но вы не можете этого сделать:

void output(int n) {
    int col=0;
    apply([&col,n](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

из-за ограничений в стандарте C ++ 11. Если вы хотите использовать захваты, вам нужно полагаться на библиотеку и

#include <functional> 

(или какую-либо другую библиотеку STL, такую ​​как алгоритм, чтобы получить ее косвенно), а затем работать с std :: function, а не передавать нормальные функции как такие параметры:

#include <functional>
void apply(std::function<void(int)> f) {
    f(10);
    f(20);
    f(30);
}
void output(int width) {
    int col;
    apply([width,&col](int data) {
        cout << data << ((++col % width) ? ' ' : '\n');
    });
}
12
ответ дан Ted 27 August 2018 в 00:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: