std::shared_ptr の基本動作

初期化、スコープによる解放、明示的解放の確認。

#include <memory>
#include <iostream>

class ToBeShared {
 public:
    ToBeShared() { std::cout << "ToBeShared Constructed. this="
                    << std::hex << this << std::endl; }
    ~ToBeShared() {std::cout << "ToBeShared Destructed. this="
                    << std::hex << this << std::endl; }
 };

int main() {
    auto printone = [](const std::string& name,
                       const std::shared_ptr<ToBeShared>& sp) {
        std::cout << name << "(" << std::hex << sp.get()
        << std::dec << ").use_count()= " << sp.use_count()
        << std::endl;
    };

    std::cout << "Creation" << std::endl;
    auto sp1 = std::make_shared<ToBeShared>();
    printone("sp1[make_shared]", sp1);
    auto sp2 = sp1;
    printone("sp2[copied sp1]", sp2);
    {   std::shared_ptr<ToBeShared> sp3;
        printone("sp3[defined]", sp3);
        sp3 = sp2;
        printone("sp3[copied sp2]", sp3);
    }  // = implicit delete
    std::cout << "sp3 out of scope." << std::endl;

    printone("sp2[before reset]", sp2);
    sp2.reset();  // = explicit delete

    printone("sp1[before reset]", sp1);
    sp1.reset();  // = explicit delete

    return 0;
}

実行結果。

Creation
ToBeShared Constructed. this=0x294f1eb03a0
sp1[make_shared](0x294f1eb03a0).use_count()= 1
sp2[copied sp1](0x294f1eb03a0).use_count()= 2
sp3[defined](0).use_count()= 0
sp3[copied sp2](0x294f1eb03a0).use_count()= 3
sp3 out of scope.
sp2[before reset](0x294f1eb03a0).use_count()= 2
sp1[before reset](0x294f1eb03a0).use_count()= 1
ToBeShared Destructed. this=0x294f1eb03a0

std::function の使い方

備忘録を兼ねて、こんな風に使うんだよというサンプルなど。
クラスのメンバ関数の場合(func1,func2)、
素の関数の場合(func3,func4)、
ラムダ関数を使う場合(func5,func6)。
それぞれ引数なし、ありの時の書き方。

#include <functional>
#include <iostream>

class class1 {
 public:
    void func1() { std::cout << "class1::func1()" << std::endl; }
    void func2(int i1, int i2) { std::cout << "class1::func2(" << i1 << "," << i2 << ")" << std::endl;  }
};

class class2 {
 public:
    std::function<void()> function1;
    std::function<void(int,int)> function2;

    std::function<void()> function3;
    std::function<void(int,int)> function4;

    std::function<void()> function5;
    std::function<void(int,int)> function6;
};

void func3() {
    std::cout << "func3()" << std::endl;
}

void func4(int i1, int i2) { std::cout << "func4(" << i1 << "," << i2 << ")" << std::endl; }

int main() {
    class1 c1;
    class2 c2;

    c2.function1 = std::bind(&class1::func1, &c1);
    c2.function2 = std::bind(&class1::func2, &c1, std::placeholders::_1, std::placeholders::_2);

    c2.function3 = std::function<void()>(func3);
    c2.function4 = std::bind(func4,std::placeholders::_1, std::placeholders::_2);

    c2.function5 = [&]() { std::cout << "lambda func5()" << std::endl; };
    c2.function6 = [&](int i1, int i2) { std::cout << "lambda func6(" << i1 << "," << i2 << ")" << std::endl; };

    c2.function1();
    c2.function2(3, 4);

    c2.function3();
    c2.function4(5, 6);

    c2.function5();
    c2.function6(7, 8);

    return 0;
}

実行結果。

class1::func1()
class1::func2(3,4)
func3()
func4(5,6)
lambda func5()
lambda func6(7,8)

「プログラミング言語C++第四版」について気が付いたことなど (22)

意味がよく分からない訳、そこまで単純じゃないという誤訳 …


42. 4   タスク ベース の 並行 処理

本章のここまでは、並行タスクを実行するためのメカニズムを中心に解説してきた。すなわち、thread、競合条件の回避、threadの同期である。この点に集中すると、並行タスクを実行する実際のタスクへの注意が散漫になってしまうのが多いことが分かっている。本節では、単純な部類のタスクに焦点を当てる。具体的には、1個の引数を受け取って、1個の結果を返すタスクである。


原文:

So far, this chapter focused on the mechanisms for running concurrent tasks: the focus was on threads, avoiding race conditions, and synchronizing threads. For many concurrent tasks, I find this focus on mechanisms distracting from the real task (sic!) of specifying concurrent tasks. This section focuses on specifying a simple kind of task: a task that does one thing given arguments and produces one result.


試訳:

本章のここまでは、並行タスクを実行するためのメカニズムにフォーカスを当てて解説してきた。すなわち、thread、競合条件の回避、threadの同期である。多くの並行処理の現場で、このような細かなメカニズムに対する集中は並行処理を記述するという本来の(文字通りの!)タスク(=作業)からの逸脱をもたらしがちである。本節では、単純な部類のタスクに焦点を当てる。それは幾つかの引数を与えられて一つのことを実行し、一つの結果を生成するタスクである。


考察:

後ろの方の訳は arguments が複数であることを見落としており、かつ does one thing が訳されていない。”one thing given arguments” を「1個の引数を受け取って」としているのかもしれないが、それはちょっと間違った捉え方。given はtaskにかかる形容詞的過去分詞、たぶん。

「プログラミング言語C++第四版」について気が付いたことなど (21)

よく似てるけど…


42.3.4  条件変数

…( condition_variable の表の中ほど)…

cv.wait(lck) 現在のスレッドがlckを所有していなければならない。自動的に lck.unlock()を呼び出してブロックする。イベント通知を受けるか、または “擬似的に通知を受ける”とブロック解除する。ブロック解除時に lck.lock() を呼び出す

原文:

cv.wait(lck) lock must be owned by the calling thread;
atomically calls lck.unlock() and blocks;
unblocks if notified or “spuriously”;
when unblocked calls lck.lock()

考察:

atomically automatically と空目しているように思われます。
この後の x=cv.wait_until(lck,tp)でも同様にatomically自動的に になってます。

尚、”spuriously” は、(notifyされなくても)処理系によって擬似的に待機解除されることがあるということなので、待機解除されたからと言って条件が成り立っているとは限らない点に注意が必要です。

 

「プログラミング言語C++第四版」について気が付いたことなど (20)

こんなもんで十分だ、と言ってるんだけど …


42.3.1.3 timed_mutex と recursive_timed_mutex

…(略)…

ここでは、画像イメージを適切な速度(この場合100ミリ秒)で更新できない場合は、新しい画像イメージをユーザに見せたほうが好ましいと仮定している。さらに、一連の画像イメージの中から1枚分の画像イメージが欠けていても、まず気付かれないだろうとも仮定している。すなわち、実際には、もっと複雑な処理が必要だ。

42. 3. 1. 4   lock_ guard と unique_ lock


原文:

The assumption here is that if the image cannot be updated reasonably fast(here, in 100 milliseconds), the user would prefer a newer version of the image. Further, it is assumed that missing an image in a sequence of updated images will rarely be noticed, so that a more complicated solution is not needed.


試訳:

ここでの仮定はもし画像が妥当な速さで(この例の場合100ミリ秒)更新できないなら、ユーザーは次のコマの画像が表示されるのを好むだろうということである。更に言うと、一連の更新画像中の一枚が欠けていてもまず気付かれることは無いと思われ、従ってこれ以上複雑な対策は不要である。


考察:

“新しい画像イメージ” は 要するに動画の次のコマという意味。
また、「すなわち」以降は意味が逆。たぶん、not を見落としている。

「プログラミング言語C++第四版」について気が付いたことなど (19)

ロックフリープログラミングにはトライするなと言ってるのに …


41.3  アトミック性

…(略)…

単純なアトミックカウンタは例外だが、ロックフリープログラミングは、専門家のためのものだ。言語の仕組みを理解するだけではなく、特定のマシンアーキテクチャの詳細な把握と、専門の実装技法の知識が欠かせない。本書で提示する内容だけに基づいて、ロックフリープログラミングを試みてはいけない。ロック技法を把握した上で論理的に高度なロックフリー技法を活用すれば、デッドロックやスタベイションなどの古典的なロック問題は発生しない。


原文:

…. Do not try lock-free programming with only the information provided here. The primary logical advantage of lock-free techniques over lock-based techniques is that classical locking problems, such as deadlock and starvation, cannot happen.


試訳:

… 本書で提供されている情報だけでロックフリープログラミングを試みないように。ロックベースの技法に対するロックフリー技法の主要な理論的優位性は、デッドロックやスターベイションのような古典的なロッキングの問題が発生し得ないということ(だけ)だ。


考察:

生半可な知識でトライするなと言ってるくせに、その直後にすばらしい技法だと勧めているような物言いは不自然に思われます。「論理的に高度なロックフリー技術」という捉え方は「logical advantage of lock-free techniques」の訳としては間違っていますし(それを言うなら logically advanced lock-free techniques ?)、それを「活用すれば」とか、どこにも書いてないし。原文で言ってるのはそういうことではないです、たぶん。

「プログラミング言語C++第四版」について気が付いたことなど (18)

そんな大げさに言わなくても … (オマケ付き)


38.4.5.3 ユーザ定義の操作子

….(略)….

さて、この宣言によって、<<と()の組合せが、単一の3項演算子に統合されることに注意しよう。cout<<sci4{d}は、実際の処理を実行する前に、ostreamと書式と値を、単一の関数に集約する

38.5  ストリーム反復子


原文:

Note that these declarations make the combination of << and () into a ternary operator;
cout<<sci4{d} collects the ostream, the format, and the value into a single function before doing any real computation.


試訳:

これらの宣言が << と () の組み合わせを三項演算子にすることに注意しよう。cout<<sci4{d} は、実際の処理を実行する前に、ostreamと書式と値を単一の関数(オブジェクト)にまとめる


考察:

Form sci4; という宣言は見当たらないけどどこかにあるとして(名前から見て sci4.scientific().precision(4) も実行されているだろう)、sci4のForm::operator()を引数dと共に呼び出す(sci4{d})ことで新たに値dと結び付けられたBound_form型のインスタンスを作り出し、それを cout::operator<<(ostream&,const Bound_form&) に渡す、という流れ。

sci4{d} は sci4(d) と書いても同じ。

Form sci4;
Bound_form bf { sci4(d) };
cout << bf;

と分けて書いても同じ(dはどこかで宣言されているものとして)。

オマケの補足: なぜsci4{d} と書くことで Form::operator() が呼び出されるのか?
ぱっと見不思議な書き方で一瞬ミスプリントじゃないかと思ったくらいですが、これはどうも、{}の初期化リストからコンパイラがinitializer_list<>を自動生成するのと、作成されたinitialize_listの要素が一個の場合は引数一個のoperator()関数の呼び出しに変換されるようで、そのため、引数一個の場合に限りこのような書き方が出来るみたいです。(違うかも、実は自信ないです)
引数が無い場合や二個ある場合は{}を使った書き方はコンパイルエラーになってしまいます。operator()の呼び出しで汎用的に()の代わりに{}が使える訳ではない模様。

 

「プログラミング言語C++第四版」について気が付いたことなど (17)

向きが逆のような気がするのだけれども …


38. 4. 4   ストリーム の 状態

…(略)…

tie()は、あるストリームからの入力を行う前に、それに結び付いたストリームからの出力を先に行うことを保証するものだ。たとえば、coutにはcinが結び付けられている:


原文:

The tie() is used to ensure that output from a tied stream appears before an input from the stream to which it is tied. For example, cout is tied to cin:


考察:

cout に cinが結び付けられているのではなくて、cout は cinに結び付けられている、つまり、cin には coutが結び付けられている、ということ。

cinがcoutの情報を持っているから、cinからの入力の実行前にcoutの出力状態をチェックすることが出来るわけ。coutがcinの情報を持っていても cin側からcoutの状態を見ることは出来ない。

ちなみに、この後ろの方では “so had cout not been tied to cin” の訳として(正しく)「cout が cinに結び付けられていなければ」となっている。

……….

とは言うものの、これはちょっと細かいところにこだわり過ぎているかも知れない。というのは、C++ referenceサイトのstd::basic_ios::tieの説明 https://en.cppreference.com/w/cpp/io/basic_ios/tie には(cout is tied to cin and cerr ではなく)下記のように書かれているから。実は結び付きの向きなんて誰も気にしてないのかも 🙂

Notes

By default, the standard streams cin and cerr are tied to cout.

 

「プログラミング言語C++第四版」について気が付いたことなど (16)

どうでもいいんだけど、命名法の違いの理由が気になる …


33.4  関数オブジェクト

(算術演算向け関数オブジェクト)

f=plus<T>{}        xとyの型がTであれば、f(x,y) は x+y を意味する。
f=minus<T>{}       xとyの型がTであれば、f(x,y) は x-y を意味する。
f=multiplies<T>{}  xとyの型がTであれば、f(x,y) は x*y を意味する。
f=divides<T>{}     xとyの型がTであれば、f(x,y) は x/y を意味する。
f=modulus<T>{}     xとyの型がTであれば、f(x,y) は x%y を意味する。
f=negate<T>{}      xの型がTであれば、f(x) は -x を意味する。

考察:

multiplyとdivideだけに三単元もしくは複数形っぽいsが付いているのは何故?

一瞬、この二つは動詞だからかと思った(plus/minusは前置詞、modulusは名詞)けど、同じく動詞のnegateはnegatesにはなっていない … 。

関数オブジェクトのテンプレートの名前なので、名詞扱いにしているのだろうとは思うのだけど。negateの対象は一つだからsが付かないと考えるなら、multipliesとdividesのsは複数形のsと見ればいいのかしらん?

いずれにしてもこれはこの本ではなくてC++のライブラリの仕様の問題ですな。