ダブルクリックで編集可能になるTextBoxをつくる2

大昔にダブルクリックで編集可能になるTextBoxを実装した(ダブルクリックで編集可能になるTextBoxをつくる - 陰間茶屋)のだが、Drag操作で難があったので違う実装をしてみることにした。

 

今度はUserControlにLabelとTextBoxを置いて、編集操作で表示を切り替える作戦。
Labelって書いたけど"_"の表示ができなかったので実際にはTextBlockになった。
コードはこんな感じになった。
もうちょっとうまい書き方があるような気はする。

EditableLabel.xaml.cs

<UserControl x:Class="Railgun.Views.EditableLabel"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Railgun.Views"
             mc:Ignorable="d" 
             d:DesignHeight="100" d:DesignWidth="200">
    <Grid>
        <!-- ラベルだと_表記が省略される。TextBlockだとダブルクリックがとれない。そのため構造を挟む -->
        <ContentControl x:Name="PART_Label"
                        Visibility="Visible">
            <TextBlock x:Name="PART_LabelText"
                       Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:EditableLabel}},
                       Path=Text}"
                       Padding="5,3,5,3"/>
        </ContentControl>

        <!-- 最初はHiddenにしておかないと領域計算ができず、ダブルクリック後のカーソル位置の計算が上手くいかない -->
        <TextBox x:Name="PART_TextBox" 
                 Visibility="Hidden"
                 AcceptsReturn="True"
                 Background="{x:Null}"
                 Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:EditableLabel}},
                        Path=Text}"/>
    </Grid>
</UserControl>


EditableLabel.xaml

using Railgun.Helper;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Railgun.Views
{
    // 参考: https://stackoverflow.com/questions/32368931/universally-usable-editable-label

    /// <summary>
    /// EditableLabel.xaml の相互作用ロジック
    /// </summary>
    public partial class EditableLabel : UserControl
    {
        public EditableLabel()
        {
            EditLabelCommand = new EditLabelCommand(this);
            InitializeComponent();

            // InputBindingのためにはKeyboardFocusが必要になる
            Focusable = true;
        }

        // bool IsEditing   編集可能か?
        public static readonly DependencyProperty IsEditingProperty =
            DependencyProperty.Register("IsEditing",
                                        typeof(bool),
                                        typeof(EditableLabel),
                                        new FrameworkPropertyMetadata(
                                            false,
                                            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                                            OnIsEditingChanged));
        public bool IsEditing {
            get { return (bool)GetValue(IsEditingProperty); }
            set { SetValue(IsEditingProperty, value); }
        }

        static void OnIsEditingChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            EditableLabel self = obj as EditableLabel;
            if (self.IsEditing) {
                self.PART_Label.Visibility = Visibility.Collapsed;
                self.PART_TextBox.Visibility = Visibility.Visible;
            }
            else {
                self.PART_Label.Visibility = Visibility.Visible;
                self.PART_TextBox.Visibility = Visibility.Collapsed;
            }
        }

        // string Text      文字列
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(EditableLabel),
                new FrameworkPropertyMetadata("",
                    FrameworkPropertyMetadataOptions.AffectsRender |
                    FrameworkPropertyMetadataOptions.AffectsParentMeasure |
                    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                    OnTextChanged));

        public string Text {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        static void OnTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {

        }

        // ICommand EditLabelCommand    編集コマンド
        public static readonly DependencyProperty EditLabelCommandProperty =
            DependencyProperty.Register("EditLabelCommand", typeof(ICommand), typeof(EditableLabel),
                new PropertyMetadata(null, OnEditLabelCommandChanged));

        public ICommand EditLabelCommand {
            get { return (ICommand)GetValue(EditLabelCommandProperty); }
            set { SetValue(EditLabelCommandProperty, value); }
        }

        static void OnEditLabelCommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            EditableLabel self = obj as EditableLabel;

            // 古いコマンドを削除
            ICommand old = e.OldValue as ICommand;
            self.RemoveInputBinding(old, Key.F2, ModifierKeys.None);

            // 新しいコマンドを登録
            ICommand command = e.NewValue as ICommand;
            if (command != null) {
                self.InputBindings.Add(new KeyBinding(command, Key.F2, ModifierKeys.None));
            }
        }

        // コマンド削除
        void RemoveInputBinding(ICommand command, Key key, ModifierKeys modifier)
        {
            foreach (InputBinding binding in InputBindings) {
                KeyBinding kb = binding as KeyBinding;
                if (kb != null) {
                    if (kb.Command == command
                        && kb.Key == key
                        && kb.Modifiers == modifier) {

                        InputBindings.Remove(binding);
                        return;
                    }
                }
            }
        }

        /*-----------------------------------------------*/

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            PART_Label.MouseDoubleClick += Label_MouseDoubleClick;
            PART_TextBox.LostKeyboardFocus += TextBox_LostKeyboardFocus;
            PART_TextBox.PreviewKeyDown += TextBox_PreviewKeyDown;

            // 他の要素をクリックしたときに編集モードを解除したいが難しい・・・
        }

        // マウスクリック
        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            base.OnMouseUp(e);

            // InputBindingのためにはKeyboardFocusが必要になる
            Focus();
            e.Handled = true;
        }


        // ラベルのダブルクリック
        void Label_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            IsEditing = true;

            // ダブルクリック後のカーソル位置を決める
            Point pos = Mouse.GetPosition(PART_TextBox);
            int textIndex = PART_TextBox.GetCharacterIndexFromPoint(pos, false);
            if (textIndex < 0) {
                textIndex = PART_TextBox.Text.Length;
            }
            PART_TextBox.Select(textIndex, 0);

            // BeginInvokeで実行しないとフォーカスが取れなかった
            Dispatcher.BeginInvoke(new Action(() => {
                PART_TextBox.Focus();
            }));
        }

        // テキストボックスのキーボードフォーカスがなくなった
        void TextBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            // 表示タイミングが合わないので明示的に入れる
            Text = PART_TextBox.Text;
            IsEditing = false;
        }

        // テキストボックスのキーが押されたときの前処理
        void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            // Enter, Escapeが押されたら編集終了
            bool isShift = ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift);
            if (e.Key == Key.Enter && !isShift) {
                // 表示タイミングが合わないので明示的に入れる
                Text = PART_TextBox.Text;
                IsEditing = false;
                e.Handled = true;
            }
            else if (e.Key == Key.Escape) {
                // この時点ではTextは編集前の値なので、ここで戻せばキャンセルが間に合う
                PART_TextBox.Text = Text;
                IsEditing = false;
                e.Handled = true;
            }
        }
    }

    // 編集可能にするコマンド
    class EditLabelCommand : DelegateCommand
    {
        public EditLabelCommand(EditableLabel editableLabel) : base(
            () => {
                editableLabel.IsEditing = true;
                editableLabel.PART_TextBox.Focus();
            }
        )
        { }
    }
}

ダブルクリックで編集モードになる。
F2キーを押したときも編集モードになる。
Enterを押すと編集完了。
Escapeを押したときも編集完了、だけど、編集前の値に戻る。

他の要素をクリックしたときに編集モードを解除させたかったんだけどやり方わからなくてできなかった。
フォーカスが違うところに行く場合なら編集モードが解除されるんだけど。
誰か教えて・・・。

WPF勉強中なんだけど分からんことが多い。
俺は雰囲気と勘と勢いでxamlを書いている。
デバッグは泣きながら行う。

参考にしたのはこのあたり
c# - Bind Command to KeyBinding in Code Behind - Stack Overflow
c# - UserControl InputBindings Only working after pressing a button first - Stack Overflow
wpf - Do mouse clicks bring keyboard focus to focusable controls by default? - Stack Overflow

ダブルクリックで編集可能になるTextBoxをつくる

WPFで、ダブルクリックで編集可能になるTextBoxをつくりたいなーと思ったので作ってみた。

Behaviorで、TextBoxのIsReadOnlyとFocusableを制御する作戦。

 

コードはこんな感じ。

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace Railgun.Helper
{
    /**
     * ダブルクリックで編集モードに移行するTextBoxになる
     * フォーカスが外れるか、Enterキーで編集終了となる
     * 
     *  <TextBox Text="hogehoge">
     *      <i:Interaction.Behaviors>
     *          <local:EditableLabelBehavior/>
     *      </i:Interaction.Behaviors>
     *  </Button>
     */
    public class EditableLabelBehavior : Behavior<TextBox>
    {
        // bool IsEditable          編集可能か?
        public static readonly DependencyProperty IsEditableProperty =
            DependencyProperty.Register("IsEditable",
                                        typeof(bool),
                                        typeof(EditableLabelBehavior),
                                        new PropertyMetadata(false, OnIsEditableChanged));

        public bool IsEditable {
            get { return (bool)GetValue(IsEditableProperty); }
            set { SetValue(IsEditableProperty, value); }
        }

        static void OnIsEditableChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            EditableLabelBehavior behavior = obj as EditableLabelBehavior;
            behavior.ApplyEditable();
        }


        // Thickness EditingBorderThickness     編集中の枠線
        public static readonly DependencyProperty EditingBorderThicknessProperty =
            DependencyProperty.Register("EditingBorderThickness",
                                typeof(Thickness),
                                typeof(EditableLabelBehavior),
                                new PropertyMetadata(new Thickness(3), OnEditingBorderThicknessChanged));

        public Thickness EditingBorderThickness {
            get { return (Thickness)GetValue(EditingBorderThicknessProperty); }
            set { SetValue(EditingBorderThicknessProperty, value); }
        }

        static void OnEditingBorderThicknessChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
        }


        // textBoxの状態を編集可・不可に切り替える
        void ApplyEditable()
        {
            if (IsEditable) {
                // 編集可
                AssociatedObject.IsReadOnly = false;
                AssociatedObject.BorderThickness = EditingBorderThickness;
                AssociatedObject.Focusable = true;
            }
            else {
                // 編集不可
                AssociatedObject.IsReadOnly = true;
                AssociatedObject.BorderThickness = _borderThickness;    // 元に戻す
                AssociatedObject.Focusable = false;
            }
        }

        Thickness _borderThickness;

        /*-----------------------------------------------*/

        // 初期化
        protected override void OnAttached()
        {
            _borderThickness = AssociatedObject.BorderThickness;
            ApplyEditable();

            AssociatedObject.MouseDoubleClick += AssociatedObject_MouseDoubleClick;
            AssociatedObject.LostFocus += AssociatedObject_LostFocus;
            AssociatedObject.KeyDown += AssociatedObject_KeyDown;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.MouseDoubleClick -= AssociatedObject_MouseDoubleClick;
            AssociatedObject.LostFocus -= AssociatedObject_LostFocus;
            AssociatedObject.KeyDown -= AssociatedObject_KeyDown;
        }


        void AssociatedObject_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            IsEditable = true;      // 編集可

            // ダブルクリック後のカーソル位置を決める
            Point pos = Mouse.GetPosition(AssociatedObject);
            int textIndex = AssociatedObject.GetCharacterIndexFromPoint(pos, false);
            if (textIndex < 0) {
                textIndex = AssociatedObject.Text.Length;
            }
            AssociatedObject.Select(textIndex, 0);

            // BeginInvokeで実行しないとフォーカスが取れなかった
            Dispatcher.BeginInvoke(new Action(() => {
                AssociatedObject.Focus();
            }));
        }

        void AssociatedObject_LostFocus(object sender, RoutedEventArgs e)
        {
            IsEditable = false;
            //Console.WriteLine("AssociatedObject_LostFocus");
        }

        void AssociatedObject_KeyDown(object sender, KeyEventArgs e)
        {
            // Enterキーが押されたら編集終了
            if (e.Key == Key.Enter) {
                IsEditable = false;
            }
        }
    }
}

使い方

    <TextBox Text="hogehoge">
        <i:Interaction.Behaviors>
            <local:EditableLabelBehavior EditingBorderThickness="3"/>
        </i:Interaction.Behaviors>
    </Button>

ゲームエディタ作ってる中で実験してたので、実はシンプルな例で試してない。
うまく動かんかったらすまぬ。

ダブルクリックで編集可能になって、フォーカスが外れると編集不可になる。
編集時はBorderThicknessの太さが変わるようにして、Enterキーで編集終了。
という動きになってる。


実装上のポイントは、
編集可能:TextBox.IsReadOnly = false; TextBox.Focusable = true;
編集不可:TextBox.IsReadOnly = true; TextBox.Focusable = false;
としているところ。これでだいたい困らないかなーと。

あと、ダブルクリックしたときはカーソル位置を決めてからフォーカスをとっている。
ただ、普通にAssociatedObject.Focus();としてもうまく動かなかったので、Dispatcher.BeginInvoke経由で実行してる。
(なお、このやり方見つけるまではmouse_eventを駆使して無理やり再クリックさせてフォーカスをとるという実装だった・・・)
その時の実装もメモ程度に載せておく。一応これでも動く

    // マウスクリック
    [DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);

    private const int MOUSEEVENTF_LEFTDOWN = 0x2;
    private const int MOUSEEVENTF_LEFTUP = 0x4;

    // ...(略)...

        // AssociatedObject.Focus()だとうまくいかなかったので、無理やりクリックさせてみる
        mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
        mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);

    // ...(略)...


参考にしたのはこのあたり:
stackoverflow.com
stackoverflow.com
www.nuits.jp

SUPER MAOU LANDで使っているカスタムRTTI

SUPER MAOU LANDで使っているカスタムRTTIの自分用メモ。


(SUPER MAOU LAND作ってます)
SUPER MAOU LAND



もともとはUnityっぽくGetComponent();などとやりたいために作り出したもの。
そのためにカスタムRTTIが必要になったので、その実装内容をメモしておく。


カスタムRTTIとかの前にC++でどうやってGetComponent();風な実装しようか考える。
するとインスタンスに対してHoge型かどうかを尋ねる関数は必要だと思う。
なのでまずこんな感じの関数を作ってみる。

//キャスト可能ならtrue
template<class T>
bool IsA(Component *component){
    return (component->getRTTI() == T::TYPE());
}

テンプレートを使うことによって、インスタンス(component)の型情報と、任意の型(T)の型情報の比較をしてるだけ。
すると、キャストする関数はこんな感じに書ける。

//キャスト(変換できないならnullptr)
template<class T>
T* Cast(Component *component){
    if( IsA<T>(component) ){
        return static_cast<T*>(component);
    }
    return nullptr;
}

先ほど定義したIsA()を呼び出して、trueならstatic_castするだけ。
これをうまく使えばGetComponent();風な実装ができそうな気がしますね?


さて、IsA()の実装内容からして、オレオレComponentにはgetRTTI()とTYPE()の実装が必要になった。
つまり、こんな感じの実装にしとく必要がある

class ComponentBase{
public:
    using RTTI = const std::string *;       //カスタムRTTIの実体は文字列のポインタ
    virtual RTTI getRTTI() const = 0;       //実装は子供に任せます
};

//コンポーネントその1
class HogeComponent : public ComponentBase{
public:
    RTTI getRTTI() const override{
        return TYPE();                      //実装内容はTYPE()に任せます
    }
    static RTTI TYPE(){                     //s_typeが型情報になります。他の型情報とかぶらない様に注意してください
        static const std::string s_type = "HogeComponent";
        return &s_type;
    }
};

//コンポーネントその2
class FugaComponent : public ComponentBase{
public:
    RTTI getRTTI() const override{
        return TYPE();                      //実装内容はTYPE()に任せます
    }
    static RTTI TYPE(){                     //s_typeが型情報になります。他の型情報とかぶらない様に注意してください
        static const std::string s_type = "FugaComponent";
        return &s_type;
    }
};


getRTTI()はインスタンスに対して呼び出されるので、仮想関数として定義しときます。
TYPE()は、T::TYPE()の様に呼び出されるので、staticメンバ関数として定義します。


さて、ここでもう一度IsA()の実装を振り返って、実装内容を確認します。
例えば、IsA(component);の呼び出しがどうなるかというと、こんな感じになります。

bool IsA(Component *component){
    return (component->getRTTI() == HogeComponent::TYPE());
}

インスタンスが持つ型情報と、型が持つ型情報が比較されています。
componentがHogeComponentであれば、getRTTI()とTYPE()が同じものを返すので、trueが返ります。
また、componentがFugaComponentであれば、getRTTI()とTYPE()が違うものを返すので、falseが返ります。


実際には、getRTTI()とTYPE()の定義がメンドイので、マクロにして使っています。

#define IMPLEMENT_COMPONENT_RTTI(className) \
    ComponentBase::RTTI getID() const override{             \
        return TYPE();                                      \
    }                                                       \
    static ComponentBase::RTTI TYPE(){                      \
        static const std::string s_type = #className;       \
        return &s_type;                                     \
    }                                                       \

すると、実装はこんな感じになります。

class ComponentBase{
public:
    using RTTI = const std::string *;       //カスタムRTTIの実体は文字列のポインタ
    virtual RTTI getRTTI() const = 0;       //実装は子供に任せます
};

//コンポーネントその1
class HogeComponent : public ComponentBase{
    //ここに好きなように色々書く
public:
    IMPLEMENT_COMPONENT_RTTI(HogeComponent);
};

//コンポーネントその2
class FugaComponent : public ComponentBase{
    //ここに好きなように色々書く
public:
    IMPLEMENT_COMPONENT_RTTI(FugaComponent);
};


わぁ、かんたん!
この仕組みはゲーム中のイベントとかにも持たせておくと、イベント毎の処理を捌きやすくなって色々と捗る。
マクロは好きくないけど他にいいやりかたが思いつかなかったのでこれでいいや、って感じ。
かしこいカスタムRTTIの実装だと継承関係とか辿ったりとかやるんだろうけど個人製作のゲームでそこまでいらんだろーって事で、ひとまずこれで。
つーかそもそもdynamic_cast使っちゃえばこんなもん実装する必要はないのだけれど・・・。


あとはこの仕組みを使ってGetComponent();できるようにするだけなんだが、その実装メモについては後で書くことにする。
後で書く。・・・といいなぁ。

ZYXの順の回転行列からオイラー角を抽出するまとめ

ZYXの順の回転行列からオイラー角を抽出するまとめ。自分用メモ。

 R = \left( \begin{array}  m_{00}&m_{01}&m_{02} \\ m_{10}&m_{11}&m_{12} \\ m_{20}&m_{21}&m_{22} \end{array} \right)
 R_x = \left( \begin{array}  1&0&0 \\ 0&cos(x)&sin(x) \\ 0&-sin(x)&cos(x) \end{array} \right)
 R_y = \left( \begin{array}  \cos(y)&0&-sin(y) \\ 0&1&0 \\ sin(y)&0&cos(y) \end{array} \right)
 R_z = \left( \begin{array}  \cos(z)&sin(z)&0 \\ -sin(z)&cos(z)&0 \\ 0&0&1 \end{array} \right)

 R_{zyx} = \left( \begin{array} \cos(z)cos(y) & sin(z)cos(x)+cos(z)sin(y)sin(x) & sin(z)sin(x)-cos(z)sin(y)cos(x) \\ -sin(z)cos(y) & cos(z)cos(x)-sin(z)sin(y)sin(x) & cos(z)sin(x)+sin(z)sin(y)cos(x) \\ sin(y) & -cos(y)sin(x) & cos(y)cos(x) \end{array} \right)


行列を眺めると、
 m_{20} = sin(y)なので、 y = asin(m_{20})
 \frac{m_{21}}{m_{22}} = \frac{-sin(x)}{cos(x)} = -tan(x)なので、 x = atan(-m_{21}, m_{22})
 \frac{m_{10}}{m_{00}} = \frac{-cos(z)}{sin(z)} = -tan(z)なので、 z = atan(-m_{10}, m_{00})


あとは、例外として、 m_{20} = sin(y) = \pm1のときを考える。( cos(y) = 0になるので上記の割り算で都合が悪い。)
こういうときは適当に x = 0として計算しちゃうのが良いらしい。
すると、 sin(x) = 0なので m_{01},  m_{11}の値が整理されるので、
 \frac{m_{01}}{m_{11}} = \frac{sin(z)cos(x)}{cos(z)cos(x)} = \frac{sin(z)}{cos(z)} = tan(z)
よって、 z = atan(m_{01}, m_{11})



参考url:
http://d.hatena.ne.jp/It_lives_vainly/20070829/1188384519
http://qiita.com/q_tarou/items/46e5045068742dfb2fa6

std::asyncめも

std::asyncなるものがあると聞いたので、とりあえずどんな感じで使えばいいのかをめも。
まずはこちらから。

#include <iostream>
#include <thread>
#include <future>
#include <chrono>
using namespace std;

class Hoge{
public:
  Hoge(){ cout << "Hoge::Hoge()" << endl; }
  ~Hoge(){ cout << "Hoge::~Hoge()" << endl; }

  void operator()(){
    cout << "thread start" << endl;
    this_thread::sleep_for(chrono::seconds(5));
    cout << "thread end" << endl;
  }
};

int main(int argc, char **argv){
  Hoge hoge;

  async(launch::async, ref(hoge)); //これはスレッドの終了を待つ
  cout << "---" << endl;

  auto f = async(launch::async, ref(hoge)); //これはfのデストラクタでスレッドの終了を待つ

  cout << "main end" << endl;
  //f.wait();
  return 0;
}

実行結果

$ ./a.exe
Hoge::Hoge()
thread start
thread end
    • -
thread start main end thread end Hoge::~Hoge()

使い方は、asyncに実行したい関数オブジェクトを渡すだけ。
async(launch::async, ref(hoge));
のように、戻り値を受け取らないように書いた場合は、そこでスレッドの終了を待つみたい。


auto f = async(launch::async, ref(hoge));
のように、戻り値(型はstd::futureになるみたい)を受け取った場合は、std::future::wait()などが用意されてるので、それを使えばスレッドの終了待ちができるみたい。
今回はwait()使ってないけど、fのデストラクタで終了待ちしてくれるみたいでした。


また、スレッドで投げた例外を受け取りたい場合、std::future::get();を使えばいいみたい。

#include <iostream>
#include <thread>
#include <future>
#include <chrono>
using namespace std;

class Hoge{
public:
  Hoge(){ cout << "Hoge::Hoge()" << endl; }
  ~Hoge(){ cout << "Hoge::~Hoge()" << endl; }

  void operator()(){
    cout << "thread start" << endl;
    this_thread::sleep_for(chrono::seconds(5));
    throw 999;
    cout << "thread end" << endl;
  }
};

int main(int argc, char **argv){
  try{
    Hoge hoge;
    auto f = async(launch::async, ref(hoge));
    cout << "---" << endl;
    f.get();
    cout << "---" << endl;
  }catch(int i){
    cout << i << endl;
  }

  cout << "main end" << endl;
  return 0;
}

実行結果

$ ./a.exe
Hoge::Hoge()
    • -
thread start Hoge::~Hoge() 999 main end

f.get();ってやってるところで、例外を受け取ってます。
すると、catch節にいくので、次のcout << "---" << endl;は呼ばれません。
get()は例外の他に、returnの値も受け取ることができるらしいです。


なんかスレッド周りの話が続いてるけど、スレッドおっ立てるのが趣味って訳じゃないです。
今日のめもおしまい。

std::threadつかってみる

std::threadの使い方メモ。
まずは基本から。

#include <iostream>
#include <thread>
using namespace std;

class Hoge{
public:
  Hoge() : m_count(0){/**/}

  void operator()(){
    for(int i=0; i<10000000; ++i){
      m_count++;
    }
  }

  int m_count;
};


int main(int argc, char **argv){
  Hoge hoge;
  thread tr(ref(hoge));
  tr.join();

  cout << hoge.m_count << endl;

  return 0;
}

処理としては別スレッドでカウントアップさせてるだけ。


operator()を実装してるオブジェクトをstd::threadに渡してあげると、スレッドが走る。終了待つときはjoin()使う。
オブジェクトのコピーを発生させたくないときは、上の例のようにref(hoge)ってやる。なので、オブジェクトコピーされてもいい場合はthread tr(hoge);でもおk。今回は最後に結果表示したかったので、ref使ってみました。


このプログラムのmainを

int main(int argc, char **argv){
  Hoge hoge;
  thread tr1(ref(hoge));
  thread tr2(ref(hoge));

  tr1.join();
  tr2.join();

  cout << hoge.m_count << endl;

  return 0;
}

ってやっちゃうと、カウンタ変数m_countがスレッド間で共有されてるので、きっちりとした値が出てきません。
えぇい!mutexだ!mutexをよこせ!!
ということでstd::mutex使ってみる。


mutex使う版

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

class Hoge{
public:
  Hoge() : m_count(0){/**/}

  void operator()(){
    for(int i=0; i<10000000; ++i){
      lock_guard<mutex> lock(m_countMutex);
      m_count++;
    }
  }

  int m_count;
  mutex m_countMutex;
};


int main(int argc, char **argv){
  Hoge hoge;
  thread tr1(ref(hoge));
  thread tr2(ref(hoge));

  tr1.join();
  tr2.join();

  cout << hoge.m_count << endl;

  return 0;
}

lock_guard lock(m_countMutex);ってやってるところでmutexをロックしてます。
アンロックしとらんじゃないかー!!って、思うかもしれないけど、lock_guardの変数の寿命が切れたときにアンロックされるので大丈夫です。
これで、20000000まできちんとカウントアップされるはずです。(´・∀・`)ヘー


なんか間違ってたらごめん

マルチスレッドでロックをかけずに読み書きするとどうなんのか

久しぶりの日記だがメモ代わりに書いとこうと思ったので書く。


マルチスレッドで同じ変数の読み書きするときはロックしましょうねーって教わったのは確かなんだが、なんで読むときもロックかけんのー?って思ったので動作確認してみた。
プログラムはこんな感じ

#include <iostream>
#include <pthread.h>
using namespace std;

typedef long long VAL;
volatile VAL g_val = 0;

void *threadFuncWrite(void *args)
{
  for(int i=0; i < 1000000; ++i){
    g_val = (i&1)? 0 : 0x1234abcd12345678LL;
  }
  return NULL;
}

void *threadFuncRead(void *args)
{
  for(int i=0; i < 1000000; ++i){
    VAL g = g_val;
    if(g!=0 && g!=0x1234abcd12345678LL){
      cout << hex << g << endl;
    }
  }
  return NULL;
}


int main(int argc, char **argv)
{
  pthread_t thw, thr;

  pthread_create(&thw, NULL, threadFuncWrite, NULL);
  pthread_create(&thr, NULL, threadFuncRead, NULL);
  
  pthread_join(thw, NULL);
  pthread_join(thr, NULL);

  return 0;
}

g_valに0か0x1234abcd12345678を書き込むスレッドと、g_valの値を読んで0か0x1234abcd12345678以外の値になってたらその値を出力するスレッド、の2つを作って走らせてるだけ。
これを何回か実行してみると、

$ ./a.exe
12345678
1234abcd00000000
12345678
12345678
12345678
1234abcd00000000
12345678
12345678
12345678
12345678
12345678
12345678
1234abcd00000000
1234abcd00000000
12345678

おぉー値が狂っておる!
再現しないときはループさせる数増やせばすぐに分かると思う。
ちなみにintとかで試したときは起こらなかったんで、1命令で読み込めるビット数とかその辺りが関係するんだろーなーとか思いつつもそこまで詳しい話は分からんかった。


とりあえず読み込みのときもロックしなくちゃいけないんだなーって思いましたまる。