C ++ 11의 람다가 기본적으로 값별 캡처를 위해 "mutable"키워드를 요구하는 이유는 무엇입니까?
간단한 예 :
#include <iostream>
int main()
{
int n;
[&](){n = 10;}(); // OK
[=]() mutable {n = 20;}(); // OK
// [=](){n = 10;}(); // Error: a by-value capture cannot be modified in a non-mutable lambda
std::cout << n << "\n"; // "10"
}
질문 : 왜 mutable
키워드 가 필요 합니까? 명명 된 함수에 전달되는 기존 매개 변수와는 상당히 다릅니다. 배후의 근거는 무엇입니까?
가치 별 포착의 요점은 사용자가 임시를 변경할 수 있다는 인상을 받았습니다.
깨달음?
(저는 MSVC2010을 사용하고 있습니다. AFAIK는 표준이어야합니다)
mutable
기본적으로 함수 객체는 호출 될 때마다 동일한 결과를 생성 해야 하기 때문에 필요합니다 . 이것은 객체 지향 함수와 전역 변수를 효과적으로 사용하는 함수의 차이입니다.
귀하의 코드는 다음과 거의 같습니다.
#include <iostream>
class unnamed1
{
int& n;
public:
unnamed1(int& N) : n(N) {}
/* OK. Your this is const but you don't modify the "n" reference,
but the value pointed by it. You wouldn't be able to modify a reference
anyway even if your operator() was mutable. When you assign a reference
it will always point to the same var.
*/
void operator()() const {n = 10;}
};
class unnamed2
{
int n;
public:
unnamed2(int N) : n(N) {}
/* OK. Your this pointer is not const (since your operator() is "mutable" instead of const).
So you can modify the "n" member. */
void operator()() {n = 20;}
};
class unnamed3
{
int n;
public:
unnamed3(int N) : n(N) {}
/* BAD. Your this is const so you can't modify the "n" member. */
void operator()() const {n = 10;}
};
int main()
{
int n;
unnamed1 u1(n); u1(); // OK
unnamed2 u2(n); u2(); // OK
//unnamed3 u3(n); u3(); // Error
std::cout << n << "\n"; // "10"
}
따라서 람다는 가변적이라고 말하지 않는 한 기본적으로 const로 설정된 operator () 클래스를 생성하는 것으로 생각할 수 있습니다.
또한 [] 내에서 (명시 적으로 또는 암시 적으로) 캡처 된 모든 변수를 해당 클래스의 구성원으로 생각할 수도 있습니다. [=]에 대한 객체의 사본 또는 [&]에 대한 객체에 대한 참조. 숨겨진 생성자가있는 것처럼 람다를 선언하면 초기화됩니다.
가치 별 포착의 요점은 사용자가 임시를 변경할 수 있다는 인상을 받았습니다.
문제는 "거의"인가? 빈번한 사용 사례는 람다를 반환하거나 전달하는 것으로 보입니다.
void registerCallback(std::function<void()> f) { /* ... */ }
void doSomething() {
std::string name = receiveName();
registerCallback([name]{ /* do something with name */ });
}
나는 그것이 mutable
"거의"경우가 아니라고 생각합니다 . "값으로 캡처"를 "캡쳐 된 개체가 사망 한 후 그 값을 사용하도록 허용하는 것"보다는 "값으로 캡처"를 고려합니다. 그러나 아마도 이것은 논쟁의 여지가 있습니다.
F ++, C ++ 표준화위원회의 유명한 멤버 인 Herb Sutter는 Lambda 정확성 및 사용성 문제 에서 해당 질문에 대한 다른 답변을 제공합니다 .
프로그래머가 값으로 로컬 변수를 캡처하고 캡처 된 값 (람다 객체의 멤버 변수)을 수정하려고하는이 straw man 예제를 고려하십시오.
int val = 0; auto x = [=](item e) // look ma, [=] means explicit copy { use(e,++val); }; // error: count is const, need ‘mutable’ auto y = [val](item e) // darnit, I really can’t get more explicit { use(e,++val); }; // same error: count is const, need ‘mutable’
이 기능은 사용자가 자신에게 사본이 있음을 인식하지 못하고 특히 람다를 복사 할 수 있기 때문에 다른 람다의 사본을 변경할 수 있다는 우려로 추가 된 것으로 보입니다.
그의 논문은 이것이 C ++ 14에서 왜 바뀌어야 하는가에 관한 것입니다. 이 특정 기능과 관련하여 "[위원회 회원] 마음에 무엇이 있는지"를 알고 싶다면 짧고 잘 쓰여지고 읽을 가치가 있습니다.
5.1.2 [expr.prim.lambda], 하위 조항 5 의이 초안을 참조하십시오 .
람다 식의 클로저 형식에는 매개 변수와 반환 형식이 각각 람다 식의 매개 변수 선언 절과 후행 반환 형식으로 설명되는 공용 인라인 함수 호출 연산자 (13.5.4)가 있습니다. 이 함수 호출 연산자는 lambdaexpression의 parameter-declaration-clause 뒤에 변경할 수없는 경우에만 const (9.3.1)로 선언됩니다.
litb의 의견 편집 : 변수에 대한 외부 변경 사항이 람다 내부에 반영되지 않도록 값으로 캡처를 생각했을 수 있습니까? 참조는 두 가지 방식으로 작동하므로 내 설명입니다. 그래도 좋은지 모르겠습니다.
kizzx2의 의견 편집 : 람다가 사용되는 대부분의 시간은 알고리즘의 functor입니다. 기본 기능을 사용하면 const
정규화 된 const
함수를 사용할 수 있지만 정규화 되지 않은 함수는 사용할 수없는 것처럼 일정한 환경에서 사용할 수 있습니다 const
. 어쩌면 그들은 자신의 마음 속에 무슨 일이 일어나고 있는지 알고있는 경우에 대해 더 직관적으로 만들려고 생각했을 수도 있습니다. :)
Lambda 함수 의 클로저 유형 이 무엇인지 생각해야 합니다. Lambda 표현식을 선언 할 때마다 컴파일러는 속성 (선언 된 Lambda 표현식이있는 환경 ) 및 함수 호출이 ::operator()
구현 된 명명되지 않은 클래스 선언에 불과한 클로저 유형을 작성합니다 . 값으로 복사를 사용하여 변수를 캡처 하면 컴파일러가 const
클로저 유형에 새 속성을 작성 하므로 "읽기 전용"속성이므로 Lambda 표현식 내에서 변경할 수 없습니다. 어떤 방식 으로든 변수를 상위 범위에서 Lambda 범위로 복사하여 Lambda 표현식을 닫고 있기 때문에 이를 " 닫기 "라고합니다.mutable
캡처 된 엔티티는 non-const
클로저 유형 의 속성이됩니다. 이것이 값으로 캡처 한 가변 변수에서 수행 된 변경 사항이 상위 범위로 전파되지 않고 상태 저장 Lambda 내부에 유지되도록하는 원인입니다. 항상 많은 도움이 된 Lambda 표현식의 결과 클로저 유형을 상상해보십시오. 도움이 될 수 있기를 바랍니다.
가치 별 포착의 요점은 사용자가 임시를 변경할 수 있다는 인상을 받았습니다.
n
일시적 이 아닙니다 . n은 람다 식으로 만든 람다 함수 객체의 멤버입니다. 기본적으로 람다를 호출해도 상태가 수정되지 않으므로 실수로 수정하지 못하도록 구성되어 있습니다 n
.
mutable
람다 선언에 대한 필요성을 완화하기위한 제안이 이제 있습니다 : n3424
캡처의 의미를 이해해야합니다! 인수 전달이 아닌 캡처입니다! 몇 가지 코드 샘플을 보자.
int main()
{
using namespace std;
int x = 5;
int y;
auto lamb = [x]() {return x + 5; };
y= lamb();
cout << y<<","<< x << endl; //outputs 10,5
x = 20;
y = lamb();
cout << y << "," << x << endl; //output 10,20
}
알 수 있듯이 람다 x
로 변경 되었지만 20
여전히 10을 반환합니다 ( x
아직 5
람다 x
내부에 있음) 람다 내부를 변경하면 각 호출에서 람다 자체가 변경됩니다 (람다는 각 호출에서 변경됩니다). 정확성을 높이기 위해 표준에 mutable
키워드가 도입되었습니다 . 람다를 변경 가능으로 지정하면 람다를 호출 할 때마다 람다 자체가 변경 될 수 있습니다. 다른 예를 보자.
int main()
{
using namespace std;
int x = 5;
int y;
auto lamb = [x]() mutable {return x++ + 5; };
y= lamb();
cout << y<<","<< x << endl; //outputs 10,5
x = 20;
y = lamb();
cout << y << "," << x << endl; //outputs 11,20
}
위의 예는 람다를 변경 가능하게하여 람다 x
내부를 변경 하면 각 호출에서 람다를 "돌연변이" 시켜 주 함수 x
의 실제 값과는 관련이없는 새로운 값을 보여줍니다.x
강아지의 대답을 확장하기 위해 람다 함수는 순수한 함수 입니다. 즉, 고유 한 입력 세트가 지정된 모든 호출은 항상 동일한 출력을 반환합니다. 람다가 호출 될 때 입력 을 모든 인수 세트와 캡처 된 모든 변수로 정의합시다 .
순수한 기능에서 출력은 일부 내부 상태가 아닌 입력에만 의존합니다. 따라서 순수한 경우 람다 함수는 상태를 변경할 필요가 없으므로 변경할 수 없습니다.
람다가 참조로 캡처 할 때 캡처 된 변수에 쓰는 것은 순수한 함수의 개념에 대한 부담입니다. 순수한 함수는 모두 출력을 반환해야하기 때문에 쓰기는 외부 변수에 발생하기 때문에 람다는 확실히 변경되지 않습니다. 이 경우에도 올바른 사용법은 동일한 입력으로 람다를 다시 호출하면 역 참조 변수에 대한 부작용에도 불구하고 매번 출력이 동일하다는 것을 의미합니다. 이러한 부작용은 일부 추가 입력을 반환하는 방법 (예 : 카운터 업데이트) 일 뿐이며 단일 값 대신 튜플을 반환하는 등 순수한 기능으로 재구성 될 수 있습니다.
'IT story' 카테고리의 다른 글
TypeError를 수정하는 방법 : 해시하기 전에 유니 코드 개체를 인코딩해야합니까? (0) | 2020.04.07 |
---|---|
스 와이프하여 삭제 및 '추가'버튼 (iOS 7의 Mail 앱에서와 같이) (0) | 2020.04.07 |
PHP는 백그라운드 프로세스를 실행 (0) | 2020.04.07 |
Erlang은 어디에 사용되며 왜 그런가요? (0) | 2020.04.07 |
Chrome 양식 자동 완성 및 노란색 배경 (0) | 2020.04.07 |