답변:
C ++에는 std::for_each
and와 같은 유용한 일반 함수가 포함되어 std::transform
있어 매우 편리합니다. 불행히도, 특히 적용하려는 펑터 가 특정 기능에 고유 한 경우 사용하기가 번거로울 수 있습니다 .
#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를 로컬로 유지하기 위해 다음과 같은 것을 작성하고 싶을 수도 있습니다.
void func2(std::vector<int>& v) {
struct {
void operator()(int) {
// do something
}
} f;
std::for_each(v.begin(), v.end(), f);
}
그러나 이것은 허용 f
되지 않으며 C ++ 03 의 템플릿 함수 로 전달 될 수 없습니다 .
C ++ 11에서는 람다를 사용하여 인라인 익명 펑터를 작성하여을 대체 할 수 struct f
있습니다. 작은 간단한 예제의 경우 읽기가 더 깨끗하고 (모든 것을 한 곳에 유지) 유지하기가 가장 단순 할 수 있습니다 (예 : 가장 간단한 형식).
void func3(std::vector<int>& v) {
std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}
람다 함수는 익명의 functors에 대한 구문 설탕입니다.
간단한 경우 람다의 반환 유형이 다음과 같이 추론됩니다.
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) { return d < 0.00001 ? 0 : d; }
);
}
그러나 더 복잡한 람다를 작성하기 시작하면 컴파일러가 반환 유형을 추론 할 수없는 경우가 발생합니다.
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;
}
});
}
지금까지 우리는 람다에 전달 된 것 이외의 것을 사용하지 않았지만 람다 내에서 다른 변수를 사용할 수도 있습니다. 다른 변수에 액세스하려면 []
지금까지 사용되지 않은 캡처 절 ( 표현식)을 사용할 수 있습니다. 예를 들면 다음과 같습니다.
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]
참조로 캡처[&]
람다에 사용 된 모든 변수를 참조로 캡처[=]
람다에 사용 된 모든 변수를 값으로 캡처[&, epsilon]
[&]와 같은 변수를 캡처하지만 값으로 엡실론[=, &epsilon]
[=]와 같은 변수를 캡처하지만 참조로 epsilon생성 된 operator()
것은 const
기본적으로 캡처가 const
기본적으로 액세스 할 때 캡처한다는 의미입니다 . 이는 동일한 입력을 가진 각 호출이 동일한 결과를 생성하는 효과가 있지만, 생성 된 호출 이 아닌 것을 요청 하도록 람다를 표시mutable
할 수 있습니다 .operator()
const
const
항상 ...
()
합니다-인수가 0 인 람다로 전달되지만 () const
람다와 일치하지 않기 때문에 암시 적 캐스트를 포함하는 형식 변환을 찾습니다. 함수 포인터를 호출 한 다음 호출합니다! 교활한!
std::function<double(int, bool)> f = [](int a, bool b) -> double { ... };
그러나 일반적으로, 우리는 종류를 추론 컴파일러를 보자 auto f = [](int a, bool b) -> double { ... };
(그리고 잊지 마세요로 #include <functional>
)
return d < 0.00001 ? 0 : d;
피연산자 중 하나가 정수 상수 일 때 (두 번째와 세 번째 피연산자가 일반적인 산술을 통해 서로 균형을 이루는? : 연산자의 암시 적 승격 규칙으로 인해) 두 배의 반환이 보장되는 이유를 모든 사람이 이해하지 못한다고 생각 합니다. 어느 것이 선택 되더라도 전환). 로 변경 0.0 : d
하면 예제를보다 쉽게 이해할 수 있습니다.
람다 함수의 C ++ 개념은 람다 미적분 및 함수형 프로그래밍에서 시작됩니다. 람다는 명명되지 않은 함수로, 재사용이 불가능하고 명명 할 가치가없는 짧은 코드 조각에 유용합니다 (이론이 아닌 실제 프로그래밍에서).
C ++에서 람다 함수는 다음과 같이 정의됩니다
[]() { } // barebone lambda
또는 모든 영광에서
[]() mutable -> T { } // T is the return type, still lacking throw()
[]
캡처 목록, ()
인수 목록 및 {}
함수 본문입니다.
캡처 목록은 람다 외부에서 함수 본문 내에서 사용 가능한 항목과 방법을 정의합니다. 다음 중 하나 일 수 있습니다.
쉼표로 구분 된 목록에서 위의 내용을 혼합 할 수 있습니다 [x, &y]
.
인수 목록은 다른 C ++ 함수와 동일합니다.
람다가 실제로 호출 될 때 실행될 코드입니다.
람다에 return 문이 하나만 있으면 반환 유형을 생략 할 수 있고 암시 적 유형은 decltype(return_statement)
입니다.
람다가 변경 가능 (예 :)으로 표시 []() mutable { }
되면 값으로 캡처 된 값을 변경하는 것이 허용됩니다.
ISO 표준에 의해 정의 된 라이브러리는 람다로부터 많은 이점을 얻었으며 이제는 사용자가 접근하기 쉬운 범위에서 작은 펑터로 코드를 어지럽 힐 필요가 없기 때문에 몇 가지 바의 유용성을 높입니다.
C ++ 14에서는 람다는 다양한 제안으로 확장되었습니다.
캡처 목록의 요소를 이제로 초기화 할 수 있습니다 =
. 이를 통해 변수의 이름을 바꾸고 이동하여 캡처 할 수 있습니다. 표준에서 가져온 예 :
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.
그리고 Wikipedia에서 가져온 것 중 하나를 사용하여 캡처하는 방법을 보여줍니다 std::move
.
auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};
람다는 이제 일반화 auto
될 수 있습니다 ( 주변 범위의 어딘가에 형식 템플릿 인수가있는 T
경우 여기
와 같습니다 T
).
auto lambda = [](auto x, auto y) {return x + y;};
C ++ 14는 모든 함수에 대해 추론 된 리턴 유형을 허용하며이를 형식의 함수로 제한하지 않습니다 return expression;
. 이것은 또한 람다로 확장됩니다.
r = &x; r += 2;
하지만 원래 값은 4입니다.
Lambda 식은 일반적으로 알고리즘을 캡슐화하여 다른 함수로 전달 될 수 있도록 사용됩니다. 그러나 정의 즉시 람다를 실행할 수 있습니다 .
[&](){ ...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!
로 프로그램 로직을 분할하는 방법 , 당신도 유용 다른 람다 식의 인수로 람다 식을 전달하는 찾을 수 있습니다 ...
[&]( std::function<void()> algorithm ) // wrapper section
{
...your wrapper code...
algorithm();
...your wrapper code...
}
([&]() // algorithm section
{
...your algorithm code...
});
Lambda 식을 사용하면 명명 된 중첩 함수 를 만들 수 있습니다.이 함수 는 중복 논리를 피하는 편리한 방법입니다. 명명되지 않은 람다를 사용하면 사소하지 않은 함수를 다른 함수에 매개 변수로 전달할 때 눈에 약간 더 쉬운 경향이 있습니다 (익명 인라인 람다에 비해). 참고 : 닫는 중괄호 뒤에 세미콜론을 잊지 마십시오.
auto algorithm = [&]( double x, double m, double b ) -> double
{
return m*x+b;
};
int a=algorithm(1,2,3), b=algorithm(4,5,6);
후속 프로파일 링에서 함수 객체에 대한 초기화 오버 헤드가 현저한 경우이를 일반 함수로 다시 작성하도록 선택할 수 있습니다.
if
: 문 if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespace
, 가정이 i
이다std::string
[](){}();
입니다..
(lambda: None)()
구문은 훨씬 더 읽기 쉽습니다.
main() {{{{((([](){{}}())));}}}}
대답
Q : C ++ 11에서 람다식이 란 무엇입니까?
A : 후드 아래에서 오버로드 연산자 () const 가있는 자동 생성 클래스의 객체입니다 . 이러한 객체를 클로저 라고 하며 컴파일러에서 생성합니다. 이 '클로저'개념은 C ++ 11의 바인드 개념에 가깝습니다. 그러나 람다는 일반적으로 더 나은 코드를 생성합니다. 클로저를 통한 호출은 완전한 인라인을 허용합니다.
Q : 언제 사용합니까?
A : "간단하고 작은 논리"를 정의하고 이전 질문에서 생성을 수행하도록 컴파일러에 요청합니다. 컴파일러에게 operator () 내부에 포함시킬 식을 제공합니다. 다른 모든 것들 컴파일러가 당신에게 생성 할 것입니다.
Q : 소개 이전에는 불가능했던 어떤 종류의 문제를 해결합니까?
A : 커스텀 추가, subrtact 연산을 위한 함수 대신 연산자 오버로딩과 같은 일종의 구문 설탕입니다 . 그러나 불필요한 클래스를 더 많이 저장하여 1-3 개의 실제 로직을 일부 클래스 등에 래핑합니다. 일부 엔지니어는 줄 수가 적 으면 오류가 발생할 가능성이 적다고 생각합니다 (저도 그렇게 생각합니다)
사용 예
auto x = [=](int arg1){printf("%i", arg1); };
void(*f)(int) = x;
f(1);
x(1);
질문에 포함되지 않은 람다에 대한 추가 정보. 관심이 없으면이 섹션을 무시하십시오.
1. 캡처 된 값. 캡처 할 수있는 것
1.1. 정적 저장 시간이 람다 인 변수를 참조 할 수 있습니다. 그들은 모두 붙잡 혔습니다.
1.2. "값별"캡처 값에 람다를 사용할 수 있습니다. 이러한 경우 캡처 된 var는 함수 객체 (클로저)에 복사됩니다.
[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. 정적이 아닌 모든 var를 값 또는 참조로 캡처하고 smth를 지정하는 표기법이 있습니다. 더. 예 : 모든 정적이 아닌 변수를 값으로 캡처하지만 참조 캡처로 Param2
[=,&Param2](int arg1){}
정적이 아닌 모든 var를 참조로 캡처하지만 값 캡처로 Param2
[&,Param2](int arg1){}
2. 반품 유형 공제
2.1. 람다가 하나의 표현식 인 경우 람다 리턴 유형을 추론 할 수 있습니다. 또는 명시 적으로 지정할 수 있습니다.
[=](int arg1)->trailing_return_type{return trailing_return_type();}
람다에 둘 이상의 표현식이있는 경우 후행 리턴 유형을 통해 리턴 유형을 지정해야합니다. 또한 자동 함수 및 멤버 함수에 유사한 구문을 적용 할 수 있습니다.
3. 캡처 된 값. 캡처 할 수없는 것
3.1. 객체의 멤버 변수가 아닌 로컬 변수 만 캡처 할 수 있습니다.
4. Сonversions
4.1 !! Lambda는 함수 포인터가 아니며 익명 함수가 아니지만 캡처리스 람다는 암시 적으로 함수 포인터로 변환 될 수 있습니다.
추신
람다 문법 정보에 대한 자세한 내용은 Programming Language C ++ # 337, 2012-01-16, 5.1.2 실무 초안에 나와 있습니다. 람다 식, p.88
C ++ 14에는 "init capture"라는 추가 기능이 추가되었습니다. 클로저 데이터 멤버를 임의로 선언 할 수 있습니다.
auto toFloat = [](int value) { return float(value);};
auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};
[&,=Param2](int arg1){}
올바른 구문 될 것 같지 않습니다. 올바른 형식은 다음과 같습니다[&,Param2](int arg1){}
람다 함수는 인라인으로 만드는 익명 함수입니다. 일부 설명 된대로 변수를 캡처 할 수 있지만 (예 : 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');
});
}
apply
functor를 수락 한 템플릿 인 경우 작동합니다
가장 좋은 설명 중 하나는 그의 책 11 장 ( ISBN-13 : 978-0321563842 ) 에서 lambda expression
C ++ Bjarne Stroustrup의 저자가 제공 한 것입니다 .***The C++ Programming Language***
What is a lambda expression?
람다 식 , 때로는 또한라고도 람다 A와 함수 또는 (엄밀히 말하자면 부정확하지만 놓고) , λ는 , 정의 및 사용하기위한 간략화 된 표기법 익명 함수 개체 . operator ()로 명명 된 클래스를 정의하고 나중에 해당 클래스의 객체를 만들고 마지막으로 호출하는 대신 속기를 사용할 수 있습니다.
When would I use one?
이것은 연산을 알고리즘에 인수로 전달할 때 특히 유용합니다. 그래픽 사용자 인터페이스 (및 다른 곳)와 관련하여 이러한 작업을 종종 콜백 이라고합니다 .
What class of problem do they solve that wasn't possible prior to their introduction?
여기에서 람다 식으로 수행 된 모든 작업을 처리하지 않고도 해결할 수 있지만 훨씬 더 많은 코드와 훨씬 더 복잡한 것으로 생각합니다. 람다 식 이것은 코드를 최적화하는 방법이며 더 매력적으로 만드는 방법입니다. Stroustup에 의해 슬프게도 :
효과적인 최적화 방법
Some examples
람다 식을 통해
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 정렬 x-1; x-3; x-4; x-5; x-6; x-7; x-33;
[]
-이것은 캡처 목록이거나 로컬 환경에 액세스 할 필요가없는 lambda introducer
경우 lambdas
사용할 수 있습니다.
책에서 인용 :
람다 식의 첫 문자는 항상 [ 입니다. 람다 소개자는 다양한 형식을 취할 수 있습니다.
• [] : 빈 캡처 목록입니다. 이는 람다 본문에서 주변 컨텍스트의 로컬 이름을 사용할 수 없음을 의미합니다. 이러한 람다 식의 경우 데이터는 인수 또는 로컬이 아닌 변수에서 가져옵니다.
• [&] : 암시 적으로 참조로 캡처합니다. 모든 로컬 이름을 사용할 수 있습니다. 모든 지역 변수는 참조로 액세스됩니다.
• [=] : 값으로 암시 적으로 캡처합니다. 모든 로컬 이름을 사용할 수 있습니다. 모든 이름은 람다 식의 호출 시점에서 가져온 로컬 변수의 복사본을 나타냅니다.
• [캡처 목록] : 명시 적 캡처; capture-list는 참조 또는 값으로 캡처 할 (즉, 오브젝트에 저장되는) 로컬 변수의 이름 목록입니다. 이름 앞에 &가 붙은 변수는 참조로 캡처됩니다. 다른 변수는 값으로 캡처됩니다. 캡처 목록에는이 이름과 요소 다음에 ...가 포함될 수 있습니다.
• [&, capture-list] : 목록에 언급되지 않은 이름을 가진 모든 지역 변수를 암시 적으로 참조하여 캡처합니다. 캡처 목록에이를 포함 할 수 있습니다. 나열된 이름 앞에는 &를 사용할 수 없습니다. 캡처 목록에 이름이 지정된 변수는 값으로 캡처됩니다.
• [=, capture-list] : 목록에 언급되지 않은 이름을 가진 모든 로컬 변수를 값으로 암시 적으로 캡처합니다. 캡처 목록에는이를 포함 할 수 없습니다. 나열된 이름 앞에 &가 와야합니다. 캡처 목록에 명명 된 변수는 참조로 캡처됩니다.
&로 시작하는 로컬 이름은 항상 참조로 캡처되며 &로 시작하지 않는 로컬 이름은 항상 값으로 캡처됩니다. 참조로만 캡처하면 호출 환경에서 변수를 수정할 수 있습니다.
Additional
Lambda expression
체재
추가 참조 :
for (int x : v) { if (x % m == 0) os << x << '\n';}
글쎄, 내가 찾은 실용적인 용도는 보일러 판 코드를 줄이는 것입니다. 예를 들면 다음과 같습니다.
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);
//...
}
람다가 없으면 다른 bsize
경우 를 위해 무언가를해야 할 수도 있습니다 . 물론 함수를 만들 수 있지만 영혼 사용자 함수의 범위 내에서 사용을 제한하려면 어떻게해야합니까? 람다의 본질은이 요구 사항을 충족하며 그 경우에 사용합니다.
C ++의 람다는 "사용 가능한 기능"으로 처리됩니다. 예, 문자 그대로 이동 중에 정의합니다. 사용해; 부모 함수 범위가 끝나면 람다 함수가 사라집니다.
c ++은 c ++ 11에 도입되었으며 모든 사람들이 가능한 모든 장소에서 사용하기 시작했습니다. 예제와 람다는 무엇입니까 https://en.cppreference.com/w/cpp/language/lambda
나는 거기에 없지만 어떤 C ++ 프로그래머에게 알아야 할지를 설명 할 것입니다.
Lambda는 모든 곳에서 사용하기위한 것이 아니며 모든 기능을 lambda로 바꿀 수는 없습니다. 또한 일반 기능과 비교할 때 가장 빠르지는 않습니다. 람다에 의해 처리되어야하는 약간의 오버 헤드가 있기 때문입니다.
경우에 따라 줄 수를 줄이는 데 도움이 될 것입니다. 기본적으로 코드 섹션에 사용할 수 있습니다. 코드 섹션은 동일한 함수에서 한 번 이상 호출되며 독립 실행 형 함수를 만들 수 있도록 다른 코드가 필요하지 않습니다.
다음은 람다의 기본 예와 배경에서 발생하는 일입니다.
사용자 코드 :
int main()
{
// Lambda & auto
int member=10;
auto endGame = [=](int a, int b){ return a+b+member;};
endGame(4,5);
return 0;
}
컴파일이 확장하는 방법 :
int main()
{
int member = 10;
class __lambda_6_18
{
int member;
public:
inline /*constexpr */ int operator()(int a, int b) const
{
return a + b + member;
}
public: __lambda_6_18(int _member)
: member{_member}
{}
};
__lambda_6_18 endGame = __lambda_6_18{member};
endGame.operator()(4, 5);
return 0;
}
보시다시피, 사용할 때 어떤 종류의 오버 헤드가 추가됩니까? 모든 곳에서 사용하는 것은 좋지 않습니다. 적용 가능한 장소에서 사용할 수 있습니다.
해결하는 한 가지 문제 : const 멤버를 초기화하기 위해 출력 매개 변수 함수를 사용하는 생성자 호출의 경우 람다보다 간단한 코드
출력을 출력 매개 변수로 돌려서 값을 설정하는 함수를 호출하여 클래스의 const 멤버를 초기화 할 수 있습니다.