SlideShare a Scribd company logo
1 of 25
Download to read offline
ITEM 31. AVOID DEFAULT
CAPTURE MODES.
MITSUTAKA TAKEDA
MITSUTAKA.TAKEDA@GMAIL.COM
TABLE OF CONTENTS
自己紹介
Chapter 6. Lambda Expressions
Item 31: Avoid default capture modes
default by-reference capture
default by-value capture
Review
おまけ
自己紹介
C++プログラマ@株式会社Skeed
ネットワーク系のライブラリ開発(C++11)
CHAPTER 6. LAMBDA EXPRESSIONS
LAMBDA
No new expressive power, but a game changer.
関数オブジェクト(ファンクタ*1)をインラインで定義できる。
*1 関数型プログラミング界隈から怒られるのでファンクタと呼ぶ
のはやめましょう
用途
std::unique_ptr、 std::shared_ptr のデリータ
STL algorithms
コール・バックの定義
API変更
用語
lambda expression: ソース・コード中に記載されている式。
image from
closure class & object: lambda expressionから生成される無
http://www.nullptr.me/2011/10/12/c11-
lambda-having-fun-with-brackets/
名関数オブジェクト・クラスとそのインスタンス。
CAPTURE MODE
  default explicit
by-value [=] [var]
by-reference [&] [&var]
void f() {
int x = 0, y = 0;
auto default_by_value = [=] {/* xとyのコピーをキャプチャ */};
auto default_by_reference = [&] {/* xとyへの参照をキャプチャ */};
auto explict_by_value = [x] {/* xのコピーをキャプチャ */};
auto explict_by_reference = [&x]{/* xへの参照をキャプチャ */};
}
ITEM 31: AVOID DEFAULT CAPTURE MODES
2 default capture modes.
by-reference
by-value
どちらのdefault capture modeも、キャプチャしたオブジェクトが
ダングリング参照になる可能性があるので使用しないほうが良
い。
DEFAULT BY-REFERENCE CAPTURE
using FilterContainer = std::vector<std::function<bool(int)> >;
FilterContainer filters;
int computeSomveValue1() { return 42; }
int computeSomveValue2() { return 7; }
int computeDivisor(int x, int y) { return 7; }
void addDivisorFilter() {
auto calc1 = computeSomveValue1();
auto calc2 = computeSomveValue2();
auto divisor = computeDivisor(calc1, calc2); // ローカル変数。
filters.emplace_back(
[&] // default by-reference capture。
(int value){ // divisor↓はローカル変数divisor↑への参照。
return (value % divisor) == 0; }
);
// addDivisorFilterが終了するとローカル変数divisorは消滅。
// キャプチャしているdivisorはダングリング参照。
}
int main() {
addDivisorFilter();
for(auto& f : filters) { // fを使用すると未定義動作。
}
}
EXPLICIT BY-REFERENCE CAPTURE
明示的にキャプチャしてもダングリング参照は回避できない。
明示的にキャプチャすることでキャプチャされている変数
(divisor)の寿命に注意できるのでbetter。 closureの寿命よりキ
ャプチャされているオブジェクトの寿命が長くなければいけない。
void addDivisorFilter() {
auto calc1 = computeSomveValue1();
auto calc2 = computeSomveValue2();
auto divisor = computeDivisor(calc1, calc2); // ローカル変数。
filters.emplace_back(
[&divisor] // <- 明示的にdivisorを参照でキャプチャ。
(int value){
return (value % divisor) == 0; }
);
// addDivisorFilterが終了するとローカル変数divisorは消滅。
// キャプチャdivisorはダングリング参照。
}
CLOSURE FOR SHORT LIFETIME
STLアルゴリズムの引数として利用するときなど、closureの寿命
が短かい時は、 default by-reference captureは安全?
ダングリング参照は起きない。しかし、lambdaがダングリング参
照を起すコンテキスト(addDivisorFilter)にコピペされてしまう危
険性が有る。
template <typename C>
void workWithContainer(const C& container) {
auto calc1 = computeSomveValue1();
auto calc2 = computeSomveValue2();
auto divisor = computeDivisor(calc1, calc2);
using ContElemT = typename C::value_type;// C++14では不要。
using std::begin; using std::end;
if(std::all_of(begin(container), end(container),
[&] // default by-value captureしているがローカル変数divisorは、クロージャよ
り寿命が長いので大丈夫。
(const ContElemT& value){ // C++14 ではGeneric Lambda (const auto& value) で
書けるよ。
return (value % divisor) == 0;
})) { /*...*/ }
}
DEFAULT BY-VALUE CAPTURE
default by-reference captureはダングリング参照が問題。
default by-value captureなら問題が無い?
void addDivisorFilter() {
auto calc1 = computeSomveValue1();
auto calc2 = computeSomveValue2();
auto divisor = computeDivisor(calc1, calc2); // ローカル変数。
filters.emplace_back(
[=] // default by-value capture
(int value){ // divisorはローカル変数のコピーなのでダングリング参照は起きない。
return (value % divisor) == 0; }
);
}
PROBLEM WITH BY-VALUE CAPTURE 1
addDivisorFilterの例ではダングリング参照の問題は解消す
る。しかし、ポインタをby-value captureすると、 pointerが
closureの外から削除された場合、ダングリング参照の問題が起
きる。
void addDivisorFilter() {
auto calc1 = computeSomveValue1();
auto calc2 = computeSomveValue2();
int* divisor = new int(computeDivisor(calc1, calc2)); // intへのポインタ。
filters.emplace_back(
[=] // <- default by-value capture
(int value){ // divisorはポインタのコピー。
return (value % (*divisor)) == 0; }
);
delete divisor; // divisorが指すオブジェクトは消滅してダングリング。
}
PROBLEM WITH BY-VALUE CAPTURE 2
スマート・ポインタを使えばダングリング問題は起きない。万事
解決?
void addDivisorFilter() {
auto calc1 = computeSomveValue1();
auto calc2 = computeSomveValue2();
auto divisor = std::make_shared<const int>(computeDivisor(calc1, calc2)); // intへ
のshared_ptr。
filters.emplace_back(
[=] // <- default by-value capture
(int value){ // closureが生きているかぎり参照カウントは0にならないのでダングリ
ングにならない。
return (value % (*divisor)) == 0; }
);
}
CAPTURE OF MEMBER VARIABLE
lambdaが定義されているスコープで見ることができるnon-static
ローカル変数と関数パラメータのみcaptureできる。
メンバ変数はどのようにキャプチャされるか。
std::vector<std::function<bool(int)> > filters;
class Widget {
public:
void addFilter() const {
filters.emplace_back(
[=] // default by-value capture。
// default([=])の代りにexplicit by-value capture([divisor])
// しようとするとコンパイル・エラー。
(int value) {
return (value % divisor) == 0; // divisorはメンバ変数のキャプチャ。
});
}
private:
int divisor;
};
CAPTURE OF MEMBER VARIABLE 2
キャプチャされるのはメンバ変数ではなくて、thisポインタ。概念
的には以下のコードと等価。
std::vector<std::function<bool(int)> > filters;
class Widget {
public:
void addFilter() const {
auto currentObjectPtr = this;
filters.emplace_back(
[currentObjectPtr] // thisポインタをキャプチャ。
(int value) {
return (value % currentObjectPtr->divisor) == 0;
});
}
private:
int divisor;
};
ところで、thisポインタを明示的にキャプチャするにはcapture
listにthisと書けば良い。
PROBLEM WITH CAPTURING MEMBER VARIABLES
thisポインタは非スマート・ポインタ。closureがthisの指すオブジ
ェクトより長く生存するとダングリング参照。 ModernなC++スタ
イル(スマート・ポインタ)でもダングリング参照は回避できない。
std::vector<std::function<bool(int)> > filters;
class Widget {
public:
void addFilter() const {
filters.emplace_back(
[=]
(int value) {
return (value % divisor) == 0;
});
}
private:
int divisor;
};
int main(int argc, char *argv[]) {
{
auto w = std::make_unique<Widget>();
w->addFilter();
} // wが指すオブジェクトは破棄されfiltersのなかにあるdivisorはダングリング。
return 0;
}
HOW TO AVOID A DANGLING REFERENCE
今回のようなケースでは、メンバ変数をローカル変数にコピーす
る(C++11)、または、generalized lambda capture(C++14 &
Item 32)で ダングリング参照を回避できる。
class Widget {
public:
void addFilter_CPP_11_Style() const {
auto divisorCopy = divisor; // ローカル変数にメンバ変数をコピー。
filters.emplace_back(
[divisorCopy] // ローカル変数のコピーをexplict by-value capture。
(int value) {
return (value % divisorCopy) == 0;
});
}
void addFilter_CPP_14_Style() const {
filters.emplace_back(
[divisor = this->divisor] // メンバ変数をgeneralized lambda captureでコピー
する。
(int value) {
return (value % divisor) == 0;
});
}
private:
int divisor;
};
ADDITIONAL DRAWBACKS OF DEFAULT BY-VALUE CAPTURE
default by-value captureは、オブジェクトを"コピーしている"の
でclosureは外部環境から独立していると錯覚させやすい。 しか
し実際にはstaticストレージのオブジェクトにも依存している。
by-valueキャプチャしているのにclosureは値のセマンティックス
ではなく参照のセマンティックスで動作する。
void addDivisorFilter() {
static auto calc1 = computeSomveValue1();
static auto calc2 = computeSomveValue2();
static auto divisor = computeDivisor(calc1, calc2); // intへのshared_ptr。
filters.emplace_back(
[=] // 何もキャプチャしていない
(int value){ // divisorはキャプチャではなく上記のstatic変数そのもの。
return (value % divisor) == 0; }
);
++divisor; // staticオブジェクトへの変更は↑のclosureへも影響する。
}
REVIEW
default captureは以下の理由から避けよう。
default by-reference captureはダングリング参照になる危険
性
default by-value captureはダングリング参照になる危険性
(特にthisポインタを通じて) & lambdaが外部環境から独立し
ていると錯覚
おまけ
IIFE IN C++ For Performance and Safety@C++ Now 2015
Imediately Invoked Function Expression
Lambda expressionを定義と同時に呼出す。
IMEDIATELY INVOKED FUNCTION EXPRESSION
条件によって初期化が異なる。
bool condition = ...;
auto size = 0; // 任意の値で初期化、または、未初期化。
if(condition){
size = 1;
} else {
size = 2;
}
// これ以降のコードではsizeはread only。sizeはconstであるべき。
IMEDIATELY INVOKED FUNCTION EXPRESSION
これならコピペされても大丈夫?
bool condition = ...;
const auto size = [&]{// default by-reference capture.
if(condition){
return 1;
} else {
return 2;
}
}(); // lambda expressionを定義と同時に呼出す。

More Related Content

What's hot

Effective Modern C++ 勉強会#3 Item 15
Effective Modern C++ 勉強会#3 Item 15Effective Modern C++ 勉強会#3 Item 15
Effective Modern C++ 勉強会#3 Item 15Mitsuru Kariya
 
Effective Modern C++ Item 9 and 10
Effective Modern C++ Item 9 and 10Effective Modern C++ Item 9 and 10
Effective Modern C++ Item 9 and 10uchan_nos
 
self_refrential_structures.pptx
self_refrential_structures.pptxself_refrential_structures.pptx
self_refrential_structures.pptxAshishNayyar12
 
C programming language tutorial
C programming language tutorial C programming language tutorial
C programming language tutorial javaTpoint s
 
templateとautoの型推論
templateとautoの型推論templateとautoの型推論
templateとautoの型推論MITSUNARI Shigeo
 
ゲーム開発者のための C++11/C++14
ゲーム開発者のための C++11/C++14ゲーム開発者のための C++11/C++14
ゲーム開発者のための C++11/C++14Ryo Suzuki
 
C++ 11 Features
C++ 11 FeaturesC++ 11 Features
C++ 11 FeaturesJan Rüegg
 
リテラル文字列型までの道
リテラル文字列型までの道リテラル文字列型までの道
リテラル文字列型までの道Satoshi Sato
 
Threads and Callbacks for Embedded Python
Threads and Callbacks for Embedded PythonThreads and Callbacks for Embedded Python
Threads and Callbacks for Embedded PythonYi-Lung Tsai
 
C++14 - Modern Programming for Demanding Times
C++14 - Modern Programming for Demanding TimesC++14 - Modern Programming for Demanding Times
C++14 - Modern Programming for Demanding TimesCarlos Miguel Ferreira
 
COMPILATION PROCESS IN C.pptx
COMPILATION PROCESS IN C.pptxCOMPILATION PROCESS IN C.pptx
COMPILATION PROCESS IN C.pptxLECO9
 
Effective Modern C++ 勉強会#3 Item16
Effective Modern C++ 勉強会#3 Item16Effective Modern C++ 勉強会#3 Item16
Effective Modern C++ 勉強会#3 Item16Mitsuru Kariya
 
Git, CMake, Conan - How to ship and reuse our C++ projects?
Git, CMake, Conan - How to ship and reuse our C++ projects?Git, CMake, Conan - How to ship and reuse our C++ projects?
Git, CMake, Conan - How to ship and reuse our C++ projects?Mateusz Pusz
 
Effective Modern C++ 勉強会#7 Item 27
Effective Modern C++ 勉強会#7 Item 27Effective Modern C++ 勉強会#7 Item 27
Effective Modern C++ 勉強会#7 Item 27Mitsuru Kariya
 
C++ Template Meta Programming の紹介@社内勉強会
C++ Template Meta Programming の紹介@社内勉強会C++ Template Meta Programming の紹介@社内勉強会
C++ Template Meta Programming の紹介@社内勉強会Akihiko Matuura
 

What's hot (20)

Effective Modern C++ 勉強会#3 Item 15
Effective Modern C++ 勉強会#3 Item 15Effective Modern C++ 勉強会#3 Item 15
Effective Modern C++ 勉強会#3 Item 15
 
Effective Modern C++ Item 9 and 10
Effective Modern C++ Item 9 and 10Effective Modern C++ Item 9 and 10
Effective Modern C++ Item 9 and 10
 
Advanced C - Part 1
Advanced C - Part 1 Advanced C - Part 1
Advanced C - Part 1
 
self_refrential_structures.pptx
self_refrential_structures.pptxself_refrential_structures.pptx
self_refrential_structures.pptx
 
C programming language tutorial
C programming language tutorial C programming language tutorial
C programming language tutorial
 
Embedded C - Lecture 4
Embedded C - Lecture 4Embedded C - Lecture 4
Embedded C - Lecture 4
 
templateとautoの型推論
templateとautoの型推論templateとautoの型推論
templateとautoの型推論
 
ゲーム開発者のための C++11/C++14
ゲーム開発者のための C++11/C++14ゲーム開発者のための C++11/C++14
ゲーム開発者のための C++11/C++14
 
C++ 11 Features
C++ 11 FeaturesC++ 11 Features
C++ 11 Features
 
リテラル文字列型までの道
リテラル文字列型までの道リテラル文字列型までの道
リテラル文字列型までの道
 
Threads and Callbacks for Embedded Python
Threads and Callbacks for Embedded PythonThreads and Callbacks for Embedded Python
Threads and Callbacks for Embedded Python
 
C++14 - Modern Programming for Demanding Times
C++14 - Modern Programming for Demanding TimesC++14 - Modern Programming for Demanding Times
C++14 - Modern Programming for Demanding Times
 
COMPILATION PROCESS IN C.pptx
COMPILATION PROCESS IN C.pptxCOMPILATION PROCESS IN C.pptx
COMPILATION PROCESS IN C.pptx
 
Effective Modern C++ 勉強会#3 Item16
Effective Modern C++ 勉強会#3 Item16Effective Modern C++ 勉強会#3 Item16
Effective Modern C++ 勉強会#3 Item16
 
Emcjp item21
Emcjp item21Emcjp item21
Emcjp item21
 
Git, CMake, Conan - How to ship and reuse our C++ projects?
Git, CMake, Conan - How to ship and reuse our C++ projects?Git, CMake, Conan - How to ship and reuse our C++ projects?
Git, CMake, Conan - How to ship and reuse our C++ projects?
 
Embedded C
Embedded CEmbedded C
Embedded C
 
Effective Modern C++ 勉強会#7 Item 27
Effective Modern C++ 勉強会#7 Item 27Effective Modern C++ 勉強会#7 Item 27
Effective Modern C++ 勉強会#7 Item 27
 
COM1407: Working with Pointers
COM1407: Working with PointersCOM1407: Working with Pointers
COM1407: Working with Pointers
 
C++ Template Meta Programming の紹介@社内勉強会
C++ Template Meta Programming の紹介@社内勉強会C++ Template Meta Programming の紹介@社内勉強会
C++ Template Meta Programming の紹介@社内勉強会
 

Similar to Emcpp item31

C++ lecture-1
C++ lecture-1C++ lecture-1
C++ lecture-1sunaemon
 
組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由kikairoya
 
C++ lecture-0
C++ lecture-0C++ lecture-0
C++ lecture-0sunaemon
 
Lambda in template_final
Lambda in template_finalLambda in template_final
Lambda in template_finalCryolite
 
C++0x in programming competition
C++0x in programming competitionC++0x in programming competition
C++0x in programming competitionyak1ex
 
C++ tips 3 カンマ演算子編
C++ tips 3 カンマ演算子編C++ tips 3 カンマ演算子編
C++ tips 3 カンマ演算子編道化師 堂華
 
C++ マルチスレッドプログラミング
C++ マルチスレッドプログラミングC++ マルチスレッドプログラミング
C++ マルチスレッドプログラミングKohsuke Yuasa
 
Cython intro prelerease
Cython intro prelereaseCython intro prelerease
Cython intro prelereaseShiqiao Du
 
Brief introduction of Boost.ICL
Brief introduction of Boost.ICLBrief introduction of Boost.ICL
Brief introduction of Boost.ICLyak1ex
 
T69 c++cli ネイティブライブラリラッピング入門
T69 c++cli ネイティブライブラリラッピング入門T69 c++cli ネイティブライブラリラッピング入門
T69 c++cli ネイティブライブラリラッピング入門伸男 伊藤
 
Wrapping a C++ library with Cython
Wrapping a C++ library with CythonWrapping a C++ library with Cython
Wrapping a C++ library with Cythonfuzzysphere
 
Boost.Flyweight
Boost.FlyweightBoost.Flyweight
Boost.Flyweightgintenlabo
 
Visual C++コード分析を支えるSAL
Visual C++コード分析を支えるSALVisual C++コード分析を支えるSAL
Visual C++コード分析を支えるSALegtra
 
Visual C++で使えるC++11
Visual C++で使えるC++11Visual C++で使えるC++11
Visual C++で使えるC++11nekko1119
 
NumPyが物足りない人へのCython入門
NumPyが物足りない人へのCython入門NumPyが物足りない人へのCython入門
NumPyが物足りない人へのCython入門Shiqiao Du
 

Similar to Emcpp item31 (20)

C++ lecture-1
C++ lecture-1C++ lecture-1
C++ lecture-1
 
組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由
 
Boost Tour 1.50.0 All
Boost Tour 1.50.0 AllBoost Tour 1.50.0 All
Boost Tour 1.50.0 All
 
Boost tour 1_40_0
Boost tour 1_40_0Boost tour 1_40_0
Boost tour 1_40_0
 
C++ lecture-0
C++ lecture-0C++ lecture-0
C++ lecture-0
 
boost tour 1.48.0 all
boost tour 1.48.0 allboost tour 1.48.0 all
boost tour 1.48.0 all
 
C++0x concept
C++0x conceptC++0x concept
C++0x concept
 
Lambda in template_final
Lambda in template_finalLambda in template_final
Lambda in template_final
 
C++0x in programming competition
C++0x in programming competitionC++0x in programming competition
C++0x in programming competition
 
C++0x総復習
C++0x総復習C++0x総復習
C++0x総復習
 
C++ tips 3 カンマ演算子編
C++ tips 3 カンマ演算子編C++ tips 3 カンマ演算子編
C++ tips 3 カンマ演算子編
 
C++ マルチスレッドプログラミング
C++ マルチスレッドプログラミングC++ マルチスレッドプログラミング
C++ マルチスレッドプログラミング
 
Cython intro prelerease
Cython intro prelereaseCython intro prelerease
Cython intro prelerease
 
Brief introduction of Boost.ICL
Brief introduction of Boost.ICLBrief introduction of Boost.ICL
Brief introduction of Boost.ICL
 
T69 c++cli ネイティブライブラリラッピング入門
T69 c++cli ネイティブライブラリラッピング入門T69 c++cli ネイティブライブラリラッピング入門
T69 c++cli ネイティブライブラリラッピング入門
 
Wrapping a C++ library with Cython
Wrapping a C++ library with CythonWrapping a C++ library with Cython
Wrapping a C++ library with Cython
 
Boost.Flyweight
Boost.FlyweightBoost.Flyweight
Boost.Flyweight
 
Visual C++コード分析を支えるSAL
Visual C++コード分析を支えるSALVisual C++コード分析を支えるSAL
Visual C++コード分析を支えるSAL
 
Visual C++で使えるC++11
Visual C++で使えるC++11Visual C++で使えるC++11
Visual C++で使えるC++11
 
NumPyが物足りない人へのCython入門
NumPyが物足りない人へのCython入門NumPyが物足りない人へのCython入門
NumPyが物足りない人へのCython入門
 

Emcpp item31

  • 1. ITEM 31. AVOID DEFAULT CAPTURE MODES. MITSUTAKA TAKEDA MITSUTAKA.TAKEDA@GMAIL.COM
  • 2. TABLE OF CONTENTS 自己紹介 Chapter 6. Lambda Expressions Item 31: Avoid default capture modes default by-reference capture default by-value capture Review おまけ
  • 4.
  • 5. CHAPTER 6. LAMBDA EXPRESSIONS
  • 6. LAMBDA No new expressive power, but a game changer. 関数オブジェクト(ファンクタ*1)をインラインで定義できる。 *1 関数型プログラミング界隈から怒られるのでファンクタと呼ぶ のはやめましょう
  • 7. 用途 std::unique_ptr、 std::shared_ptr のデリータ STL algorithms コール・バックの定義 API変更
  • 8. 用語 lambda expression: ソース・コード中に記載されている式。 image from closure class & object: lambda expressionから生成される無 http://www.nullptr.me/2011/10/12/c11- lambda-having-fun-with-brackets/
  • 9. 名関数オブジェクト・クラスとそのインスタンス。 CAPTURE MODE   default explicit by-value [=] [var] by-reference [&] [&var] void f() { int x = 0, y = 0; auto default_by_value = [=] {/* xとyのコピーをキャプチャ */}; auto default_by_reference = [&] {/* xとyへの参照をキャプチャ */}; auto explict_by_value = [x] {/* xのコピーをキャプチャ */}; auto explict_by_reference = [&x]{/* xへの参照をキャプチャ */}; }
  • 10. ITEM 31: AVOID DEFAULT CAPTURE MODES 2 default capture modes. by-reference by-value どちらのdefault capture modeも、キャプチャしたオブジェクトが ダングリング参照になる可能性があるので使用しないほうが良 い。
  • 11. DEFAULT BY-REFERENCE CAPTURE using FilterContainer = std::vector<std::function<bool(int)> >; FilterContainer filters; int computeSomveValue1() { return 42; } int computeSomveValue2() { return 7; } int computeDivisor(int x, int y) { return 7; } void addDivisorFilter() { auto calc1 = computeSomveValue1(); auto calc2 = computeSomveValue2(); auto divisor = computeDivisor(calc1, calc2); // ローカル変数。 filters.emplace_back( [&] // default by-reference capture。 (int value){ // divisor↓はローカル変数divisor↑への参照。 return (value % divisor) == 0; } ); // addDivisorFilterが終了するとローカル変数divisorは消滅。 // キャプチャしているdivisorはダングリング参照。 } int main() { addDivisorFilter(); for(auto& f : filters) { // fを使用すると未定義動作。 } }
  • 12. EXPLICIT BY-REFERENCE CAPTURE 明示的にキャプチャしてもダングリング参照は回避できない。 明示的にキャプチャすることでキャプチャされている変数 (divisor)の寿命に注意できるのでbetter。 closureの寿命よりキ ャプチャされているオブジェクトの寿命が長くなければいけない。 void addDivisorFilter() { auto calc1 = computeSomveValue1(); auto calc2 = computeSomveValue2(); auto divisor = computeDivisor(calc1, calc2); // ローカル変数。 filters.emplace_back( [&divisor] // <- 明示的にdivisorを参照でキャプチャ。 (int value){ return (value % divisor) == 0; } ); // addDivisorFilterが終了するとローカル変数divisorは消滅。 // キャプチャdivisorはダングリング参照。 }
  • 13. CLOSURE FOR SHORT LIFETIME STLアルゴリズムの引数として利用するときなど、closureの寿命 が短かい時は、 default by-reference captureは安全? ダングリング参照は起きない。しかし、lambdaがダングリング参 照を起すコンテキスト(addDivisorFilter)にコピペされてしまう危 険性が有る。 template <typename C> void workWithContainer(const C& container) { auto calc1 = computeSomveValue1(); auto calc2 = computeSomveValue2(); auto divisor = computeDivisor(calc1, calc2); using ContElemT = typename C::value_type;// C++14では不要。 using std::begin; using std::end; if(std::all_of(begin(container), end(container), [&] // default by-value captureしているがローカル変数divisorは、クロージャよ り寿命が長いので大丈夫。 (const ContElemT& value){ // C++14 ではGeneric Lambda (const auto& value) で 書けるよ。 return (value % divisor) == 0; })) { /*...*/ } }
  • 14. DEFAULT BY-VALUE CAPTURE default by-reference captureはダングリング参照が問題。 default by-value captureなら問題が無い? void addDivisorFilter() { auto calc1 = computeSomveValue1(); auto calc2 = computeSomveValue2(); auto divisor = computeDivisor(calc1, calc2); // ローカル変数。 filters.emplace_back( [=] // default by-value capture (int value){ // divisorはローカル変数のコピーなのでダングリング参照は起きない。 return (value % divisor) == 0; } ); }
  • 15. PROBLEM WITH BY-VALUE CAPTURE 1 addDivisorFilterの例ではダングリング参照の問題は解消す る。しかし、ポインタをby-value captureすると、 pointerが closureの外から削除された場合、ダングリング参照の問題が起 きる。 void addDivisorFilter() { auto calc1 = computeSomveValue1(); auto calc2 = computeSomveValue2(); int* divisor = new int(computeDivisor(calc1, calc2)); // intへのポインタ。 filters.emplace_back( [=] // <- default by-value capture (int value){ // divisorはポインタのコピー。 return (value % (*divisor)) == 0; } ); delete divisor; // divisorが指すオブジェクトは消滅してダングリング。 }
  • 16. PROBLEM WITH BY-VALUE CAPTURE 2 スマート・ポインタを使えばダングリング問題は起きない。万事 解決? void addDivisorFilter() { auto calc1 = computeSomveValue1(); auto calc2 = computeSomveValue2(); auto divisor = std::make_shared<const int>(computeDivisor(calc1, calc2)); // intへ のshared_ptr。 filters.emplace_back( [=] // <- default by-value capture (int value){ // closureが生きているかぎり参照カウントは0にならないのでダングリ ングにならない。 return (value % (*divisor)) == 0; } ); }
  • 17. CAPTURE OF MEMBER VARIABLE lambdaが定義されているスコープで見ることができるnon-static ローカル変数と関数パラメータのみcaptureできる。 メンバ変数はどのようにキャプチャされるか。 std::vector<std::function<bool(int)> > filters; class Widget { public: void addFilter() const { filters.emplace_back( [=] // default by-value capture。 // default([=])の代りにexplicit by-value capture([divisor]) // しようとするとコンパイル・エラー。 (int value) { return (value % divisor) == 0; // divisorはメンバ変数のキャプチャ。 }); } private: int divisor; };
  • 18. CAPTURE OF MEMBER VARIABLE 2 キャプチャされるのはメンバ変数ではなくて、thisポインタ。概念 的には以下のコードと等価。 std::vector<std::function<bool(int)> > filters; class Widget { public: void addFilter() const { auto currentObjectPtr = this; filters.emplace_back( [currentObjectPtr] // thisポインタをキャプチャ。 (int value) { return (value % currentObjectPtr->divisor) == 0; }); } private: int divisor; }; ところで、thisポインタを明示的にキャプチャするにはcapture listにthisと書けば良い。
  • 19. PROBLEM WITH CAPTURING MEMBER VARIABLES thisポインタは非スマート・ポインタ。closureがthisの指すオブジ ェクトより長く生存するとダングリング参照。 ModernなC++スタ イル(スマート・ポインタ)でもダングリング参照は回避できない。 std::vector<std::function<bool(int)> > filters; class Widget { public: void addFilter() const { filters.emplace_back( [=] (int value) { return (value % divisor) == 0; }); } private: int divisor; }; int main(int argc, char *argv[]) { { auto w = std::make_unique<Widget>(); w->addFilter(); } // wが指すオブジェクトは破棄されfiltersのなかにあるdivisorはダングリング。 return 0; }
  • 20. HOW TO AVOID A DANGLING REFERENCE 今回のようなケースでは、メンバ変数をローカル変数にコピーす る(C++11)、または、generalized lambda capture(C++14 & Item 32)で ダングリング参照を回避できる。 class Widget { public: void addFilter_CPP_11_Style() const { auto divisorCopy = divisor; // ローカル変数にメンバ変数をコピー。 filters.emplace_back( [divisorCopy] // ローカル変数のコピーをexplict by-value capture。 (int value) { return (value % divisorCopy) == 0; }); } void addFilter_CPP_14_Style() const { filters.emplace_back( [divisor = this->divisor] // メンバ変数をgeneralized lambda captureでコピー する。 (int value) { return (value % divisor) == 0; }); } private: int divisor; };
  • 21. ADDITIONAL DRAWBACKS OF DEFAULT BY-VALUE CAPTURE default by-value captureは、オブジェクトを"コピーしている"の でclosureは外部環境から独立していると錯覚させやすい。 しか し実際にはstaticストレージのオブジェクトにも依存している。 by-valueキャプチャしているのにclosureは値のセマンティックス ではなく参照のセマンティックスで動作する。 void addDivisorFilter() { static auto calc1 = computeSomveValue1(); static auto calc2 = computeSomveValue2(); static auto divisor = computeDivisor(calc1, calc2); // intへのshared_ptr。 filters.emplace_back( [=] // 何もキャプチャしていない (int value){ // divisorはキャプチャではなく上記のstatic変数そのもの。 return (value % divisor) == 0; } ); ++divisor; // staticオブジェクトへの変更は↑のclosureへも影響する。 }
  • 22. REVIEW default captureは以下の理由から避けよう。 default by-reference captureはダングリング参照になる危険 性 default by-value captureはダングリング参照になる危険性 (特にthisポインタを通じて) & lambdaが外部環境から独立し ていると錯覚
  • 23. おまけ IIFE IN C++ For Performance and Safety@C++ Now 2015 Imediately Invoked Function Expression Lambda expressionを定義と同時に呼出す。
  • 24. IMEDIATELY INVOKED FUNCTION EXPRESSION 条件によって初期化が異なる。 bool condition = ...; auto size = 0; // 任意の値で初期化、または、未初期化。 if(condition){ size = 1; } else { size = 2; } // これ以降のコードではsizeはread only。sizeはconstであるべき。
  • 25. IMEDIATELY INVOKED FUNCTION EXPRESSION これならコピペされても大丈夫? bool condition = ...; const auto size = [&]{// default by-reference capture. if(condition){ return 1; } else { return 2; } }(); // lambda expressionを定義と同時に呼出す。