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++勉強会#4 Item 17, 18資料
Effective Modern C++勉強会#4 Item 17, 18資料Effective Modern C++勉強会#4 Item 17, 18資料
Effective Modern C++勉強会#4 Item 17, 18資料Ryo Igarashi
 
Effective Modern C++ 勉強会#3 Item16
Effective Modern C++ 勉強会#3 Item16Effective Modern C++ 勉強会#3 Item16
Effective Modern C++ 勉強会#3 Item16Mitsuru Kariya
 
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++ 勉強会#7 Item 27
Effective Modern C++ 勉強会#7 Item 27Effective Modern C++ 勉強会#7 Item 27
Effective Modern C++ 勉強会#7 Item 27Mitsuru Kariya
 
組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由kikairoya
 
templateとautoの型推論
templateとautoの型推論templateとautoの型推論
templateとautoの型推論MITSUNARI Shigeo
 
クソザコ鳥頭が非順序連想コンテナに入門してみた
クソザコ鳥頭が非順序連想コンテナに入門してみたクソザコ鳥頭が非順序連想コンテナに入門してみた
クソザコ鳥頭が非順序連想コンテナに入門してみたMitsuru Kariya
 
unique_ptrにポインタ以外のものを持たせるとき
unique_ptrにポインタ以外のものを持たせるときunique_ptrにポインタ以外のものを持たせるとき
unique_ptrにポインタ以外のものを持たせるときShintarou Okada
 
[C++ Korea] Effective Modern C++ Study, Item 27, 29 - 30
[C++ Korea] Effective Modern C++ Study, Item 27, 29 - 30[C++ Korea] Effective Modern C++ Study, Item 27, 29 - 30
[C++ Korea] Effective Modern C++ Study, Item 27, 29 - 30Chris Ohk
 
Visual C++で使えるC++11
Visual C++で使えるC++11Visual C++で使えるC++11
Visual C++で使えるC++11nekko1119
 
C++ ポインタ ブートキャンプ
C++ ポインタ ブートキャンプC++ ポインタ ブートキャンプ
C++ ポインタ ブートキャンプKohsuke Yuasa
 
良い?悪い?コードコメントの書き方
良い?悪い?コードコメントの書き方良い?悪い?コードコメントの書き方
良い?悪い?コードコメントの書き方Shigenori Sagawa
 
すごいConstたのしく使おう!
すごいConstたのしく使おう!すごいConstたのしく使おう!
すごいConstたのしく使おう!Akihiro Nishimura
 
クロージャデザインパターン
クロージャデザインパターンクロージャデザインパターン
クロージャデザインパターンMoriharu Ohzu
 
PWNの超入門 大和セキュリティ神戸 2018-03-25
PWNの超入門 大和セキュリティ神戸 2018-03-25PWNの超入門 大和セキュリティ神戸 2018-03-25
PWNの超入門 大和セキュリティ神戸 2018-03-25Isaac Mathis
 
Visual C++コード分析を支えるSAL
Visual C++コード分析を支えるSALVisual C++コード分析を支えるSAL
Visual C++コード分析を支えるSALegtra
 
C++の話(本当にあった怖い話)
C++の話(本当にあった怖い話)C++の話(本当にあった怖い話)
C++の話(本当にあった怖い話)Yuki Tamura
 
「日本語LaTeX」が多すぎる件について
「日本語LaTeX」が多すぎる件について「日本語LaTeX」が多すぎる件について
「日本語LaTeX」が多すぎる件についてTakayuki Yato
 
Effective Modern C++ 勉強会 Item26
Effective Modern C++ 勉強会 Item26Effective Modern C++ 勉強会 Item26
Effective Modern C++ 勉強会 Item26Akihiro Nishimura
 
LR parsing
LR parsingLR parsing
LR parsingichikaz3
 

What's hot (20)

Effective Modern C++勉強会#4 Item 17, 18資料
Effective Modern C++勉強会#4 Item 17, 18資料Effective Modern C++勉強会#4 Item 17, 18資料
Effective Modern C++勉強会#4 Item 17, 18資料
 
Effective Modern C++ 勉強会#3 Item16
Effective Modern C++ 勉強会#3 Item16Effective Modern C++ 勉強会#3 Item16
Effective Modern C++ 勉強会#3 Item16
 
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++ 勉強会#7 Item 27
Effective Modern C++ 勉強会#7 Item 27Effective Modern C++ 勉強会#7 Item 27
Effective Modern C++ 勉強会#7 Item 27
 
組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由
 
templateとautoの型推論
templateとautoの型推論templateとautoの型推論
templateとautoの型推論
 
クソザコ鳥頭が非順序連想コンテナに入門してみた
クソザコ鳥頭が非順序連想コンテナに入門してみたクソザコ鳥頭が非順序連想コンテナに入門してみた
クソザコ鳥頭が非順序連想コンテナに入門してみた
 
unique_ptrにポインタ以外のものを持たせるとき
unique_ptrにポインタ以外のものを持たせるときunique_ptrにポインタ以外のものを持たせるとき
unique_ptrにポインタ以外のものを持たせるとき
 
[C++ Korea] Effective Modern C++ Study, Item 27, 29 - 30
[C++ Korea] Effective Modern C++ Study, Item 27, 29 - 30[C++ Korea] Effective Modern C++ Study, Item 27, 29 - 30
[C++ Korea] Effective Modern C++ Study, Item 27, 29 - 30
 
Visual C++で使えるC++11
Visual C++で使えるC++11Visual C++で使えるC++11
Visual C++で使えるC++11
 
C++ ポインタ ブートキャンプ
C++ ポインタ ブートキャンプC++ ポインタ ブートキャンプ
C++ ポインタ ブートキャンプ
 
良い?悪い?コードコメントの書き方
良い?悪い?コードコメントの書き方良い?悪い?コードコメントの書き方
良い?悪い?コードコメントの書き方
 
すごいConstたのしく使おう!
すごいConstたのしく使おう!すごいConstたのしく使おう!
すごいConstたのしく使おう!
 
クロージャデザインパターン
クロージャデザインパターンクロージャデザインパターン
クロージャデザインパターン
 
PWNの超入門 大和セキュリティ神戸 2018-03-25
PWNの超入門 大和セキュリティ神戸 2018-03-25PWNの超入門 大和セキュリティ神戸 2018-03-25
PWNの超入門 大和セキュリティ神戸 2018-03-25
 
Visual C++コード分析を支えるSAL
Visual C++コード分析を支えるSALVisual C++コード分析を支えるSAL
Visual C++コード分析を支えるSAL
 
C++の話(本当にあった怖い話)
C++の話(本当にあった怖い話)C++の話(本当にあった怖い話)
C++の話(本当にあった怖い話)
 
「日本語LaTeX」が多すぎる件について
「日本語LaTeX」が多すぎる件について「日本語LaTeX」が多すぎる件について
「日本語LaTeX」が多すぎる件について
 
Effective Modern C++ 勉強会 Item26
Effective Modern C++ 勉強会 Item26Effective Modern C++ 勉強会 Item26
Effective Modern C++ 勉強会 Item26
 
LR parsing
LR parsingLR parsing
LR parsing
 

Similar to Emcpp item31

C++ lecture-1
C++ lecture-1C++ lecture-1
C++ lecture-1sunaemon
 
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
 
NumPyが物足りない人へのCython入門
NumPyが物足りない人へのCython入門NumPyが物足りない人へのCython入門
NumPyが物足りない人へのCython入門Shiqiao Du
 
“Symbolic bounds analysis of pointers, array indices, and accessed memory reg...
“Symbolic bounds analysis of pointers, array indices, and accessed memory reg...“Symbolic bounds analysis of pointers, array indices, and accessed memory reg...
“Symbolic bounds analysis of pointers, array indices, and accessed memory reg...Masahiro Sakai
 
Introduction to cython
Introduction to cythonIntroduction to cython
Introduction to cythonAtsuo Ishimoto
 

Similar to Emcpp item31 (20)

C++ lecture-1
C++ lecture-1C++ lecture-1
C++ lecture-1
 
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
 
NumPyが物足りない人へのCython入門
NumPyが物足りない人へのCython入門NumPyが物足りない人へのCython入門
NumPyが物足りない人へのCython入門
 
Pfi Seminar 2010 1 7
Pfi Seminar 2010 1 7Pfi Seminar 2010 1 7
Pfi Seminar 2010 1 7
 
“Symbolic bounds analysis of pointers, array indices, and accessed memory reg...
“Symbolic bounds analysis of pointers, array indices, and accessed memory reg...“Symbolic bounds analysis of pointers, array indices, and accessed memory reg...
“Symbolic bounds analysis of pointers, array indices, and accessed memory reg...
 
Introduction to cython
Introduction to cythonIntroduction to cython
Introduction to 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を定義と同時に呼出す。