SlideShare a Scribd company logo
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)
Emcpp item31
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を定義と同時に呼出す。
Ad

More Related Content

What's hot (20)

証明プログラミング超入門
証明プログラミング超入門証明プログラミング超入門
証明プログラミング超入門
Kyoko Kadowaki
 
SAT/SMTソルバの仕組み
SAT/SMTソルバの仕組みSAT/SMTソルバの仕組み
SAT/SMTソルバの仕組み
Masahiro Sakai
 
外部環境への依存をテストする
外部環境への依存をテストする外部環境への依存をテストする
外部環境への依存をテストする
Shunsuke Maeda
 
In-Depth Model/View with QML
In-Depth Model/View with QMLIn-Depth Model/View with QML
In-Depth Model/View with QML
ICS
 
Rust vs C++
Rust vs C++Rust vs C++
Rust vs C++
corehard_by
 
Effective modern c++ 5
Effective modern c++ 5Effective modern c++ 5
Effective modern c++ 5
uchan_nos
 
C++の黒魔術
C++の黒魔術C++の黒魔術
C++の黒魔術
Daichi OBINATA
 
Deep C
Deep CDeep C
Deep C
Olve Maudal
 
Modern C++
Modern C++Modern C++
Modern C++
Richard Thomson
 
Vim scriptとJavaとHaskell
Vim scriptとJavaとHaskellVim scriptとJavaとHaskell
Vim scriptとJavaとHaskell
aiya000
 
JavaScript: Variables and Functions
JavaScript: Variables and FunctionsJavaScript: Variables and Functions
JavaScript: Variables and Functions
Jussi Pohjolainen
 
Qt programming-using-cpp
Qt programming-using-cppQt programming-using-cpp
Qt programming-using-cpp
Emertxe Information Technologies Pvt Ltd
 
すごいHaskell 第7章 型や型クラスを自分で作ろう(前編)
すごいHaskell 第7章 型や型クラスを自分で作ろう(前編)すごいHaskell 第7章 型や型クラスを自分で作ろう(前編)
すごいHaskell 第7章 型や型クラスを自分で作ろう(前編)
Nozomu Kaneko
 
Advanced Python : Static and Class Methods
Advanced Python : Static and Class Methods Advanced Python : Static and Class Methods
Advanced Python : Static and Class Methods
Bhanwar Singh Meena
 
冬のLock free祭り safe
冬のLock free祭り safe冬のLock free祭り safe
冬のLock free祭り safe
Kumazaki Hiroki
 
Kotlin on android
Kotlin on androidKotlin on android
Kotlin on android
Kurt Renzo Acosta
 
constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ
constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだconstexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ
constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ
Genya Murakami
 
Fun with Lambdas: C++14 Style (part 1)
Fun with Lambdas: C++14 Style (part 1)Fun with Lambdas: C++14 Style (part 1)
Fun with Lambdas: C++14 Style (part 1)
Sumant Tambe
 
闇魔術を触ってみた
闇魔術を触ってみた闇魔術を触ってみた
闇魔術を触ってみた
Satoshi Sato
 
証明プログラミング超入門
証明プログラミング超入門証明プログラミング超入門
証明プログラミング超入門
Kyoko Kadowaki
 
SAT/SMTソルバの仕組み
SAT/SMTソルバの仕組みSAT/SMTソルバの仕組み
SAT/SMTソルバの仕組み
Masahiro Sakai
 
外部環境への依存をテストする
外部環境への依存をテストする外部環境への依存をテストする
外部環境への依存をテストする
Shunsuke Maeda
 
In-Depth Model/View with QML
In-Depth Model/View with QMLIn-Depth Model/View with QML
In-Depth Model/View with QML
ICS
 
Effective modern c++ 5
Effective modern c++ 5Effective modern c++ 5
Effective modern c++ 5
uchan_nos
 
Vim scriptとJavaとHaskell
Vim scriptとJavaとHaskellVim scriptとJavaとHaskell
Vim scriptとJavaとHaskell
aiya000
 
JavaScript: Variables and Functions
JavaScript: Variables and FunctionsJavaScript: Variables and Functions
JavaScript: Variables and Functions
Jussi Pohjolainen
 
すごいHaskell 第7章 型や型クラスを自分で作ろう(前編)
すごいHaskell 第7章 型や型クラスを自分で作ろう(前編)すごいHaskell 第7章 型や型クラスを自分で作ろう(前編)
すごいHaskell 第7章 型や型クラスを自分で作ろう(前編)
Nozomu Kaneko
 
Advanced Python : Static and Class Methods
Advanced Python : Static and Class Methods Advanced Python : Static and Class Methods
Advanced Python : Static and Class Methods
Bhanwar Singh Meena
 
冬のLock free祭り safe
冬のLock free祭り safe冬のLock free祭り safe
冬のLock free祭り safe
Kumazaki Hiroki
 
constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ
constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだconstexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ
constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ
Genya Murakami
 
Fun with Lambdas: C++14 Style (part 1)
Fun with Lambdas: C++14 Style (part 1)Fun with Lambdas: C++14 Style (part 1)
Fun with Lambdas: C++14 Style (part 1)
Sumant Tambe
 
闇魔術を触ってみた
闇魔術を触ってみた闇魔術を触ってみた
闇魔術を触ってみた
Satoshi Sato
 

Similar to Emcpp item31 (20)

C++ lecture-1
C++ lecture-1C++ lecture-1
C++ lecture-1
sunaemon
 
組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由
kikairoya
 
Boost tour 1_40_0
Boost tour 1_40_0Boost tour 1_40_0
Boost tour 1_40_0
Akira Takahashi
 
C++ lecture-0
C++ lecture-0C++ lecture-0
C++ lecture-0
sunaemon
 
Lambda in template_final
Lambda in template_finalLambda in template_final
Lambda in template_final
Cryolite
 
C++0x in programming competition
C++0x in programming competitionC++0x in programming competition
C++0x in programming competition
yak1ex
 
C++0x総復習
C++0x総復習C++0x総復習
C++0x総復習
道化師 堂華
 
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 prelerease
Shiqiao Du
 
Brief introduction of Boost.ICL
Brief introduction of Boost.ICLBrief introduction of Boost.ICL
Brief introduction of Boost.ICL
yak1ex
 
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
fuzzysphere
 
Boost.Flyweight
Boost.FlyweightBoost.Flyweight
Boost.Flyweight
gintenlabo
 
Visual C++コード分析を支えるSAL
Visual C++コード分析を支えるSALVisual C++コード分析を支えるSAL
Visual C++コード分析を支えるSAL
egtra
 
Visual C++で使えるC++11
Visual C++で使えるC++11Visual C++で使えるC++11
Visual C++で使えるC++11
nekko1119
 
NumPyが物足りない人へのCython入門
NumPyが物足りない人へのCython入門NumPyが物足りない人へのCython入門
NumPyが物足りない人へのCython入門
Shiqiao Du
 
C++ lecture-1
C++ lecture-1C++ lecture-1
C++ lecture-1
sunaemon
 
組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由
kikairoya
 
C++ lecture-0
C++ lecture-0C++ lecture-0
C++ lecture-0
sunaemon
 
Lambda in template_final
Lambda in template_finalLambda in template_final
Lambda in template_final
Cryolite
 
C++0x in programming competition
C++0x in programming competitionC++0x in programming competition
C++0x in programming competition
yak1ex
 
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 prelerease
Shiqiao Du
 
Brief introduction of Boost.ICL
Brief introduction of Boost.ICLBrief introduction of Boost.ICL
Brief introduction of Boost.ICL
yak1ex
 
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
fuzzysphere
 
Boost.Flyweight
Boost.FlyweightBoost.Flyweight
Boost.Flyweight
gintenlabo
 
Visual C++コード分析を支えるSAL
Visual C++コード分析を支えるSALVisual C++コード分析を支えるSAL
Visual C++コード分析を支えるSAL
egtra
 
Visual C++で使えるC++11
Visual C++で使えるC++11Visual C++で使えるC++11
Visual C++で使えるC++11
nekko1119
 
NumPyが物足りない人へのCython入門
NumPyが物足りない人へのCython入門NumPyが物足りない人へのCython入門
NumPyが物足りない人へのCython入門
Shiqiao Du
 
Ad

Emcpp item31

  • 1. ITEM 31. AVOID DEFAULT CAPTURE MODES. MITSUTAKA TAKEDA [email protected]
  • 2. TABLE OF CONTENTS 自己紹介 Chapter 6. Lambda Expressions Item 31: Avoid default capture modes default by-reference capture default by-value capture Review おまけ
  • 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を定義と同時に呼出す。