TechsMex

技術エンジニア C++/Python/Java

Bill Pugh singleton (initilization-on-demand holder idiom)

概要

Singletonの実装方法を探している際に見つけたエレガントで感動した書き方について紹介する。

一般的なSingletonの実装方法

下記に一般的な実装方法を紹介する。

public class GeneralSingleton {
    private static GeneralSingleton instance = new GeneralSingleton();

    private GeneralSingleton() {}

    private static GeneralSingleton getInstance() {
        return instance;
    }
}

もしくは遅延評価させるために下記の例もよく見られる。

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {
    }

    private static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

Bill Pugh Singleton

Bill Pughは考案者の名前である。このSingletonは一言で言うとInitilization-on-demand holder idiomを適用させたSingletonらしい。

public class BillPughSingleton {
    private BillPughSingleton() {}

    public static BillPughSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }
}

このパターンの肝はJavaの仕様とInner classであるSingletonHolderにある。

JVMにBill Pugh Singletonがloadされた場合、一旦は初期化は無視される。

そして、SingletonHolderの中のINSTANCEは評価されるまで初期化されないため、呼び出された際に初めて初期化される。

この時、最初に呼び出したスレッドにおいてこの値は確定するため、自然にスレッドセーフになるという特徴を備えている。

ここら辺は正直完全に理解できていないため、後日追記する。

一般的なSingletonとの違い

先に二つのSingletonを紹介したが、下記のような欠点がある。

  • GeneralSingltonでは遅延初期化を行わないため、インスタンスが大きい場合JVM起動時に処理が遅くなる。

  • LazySingletonはスレッドセーフでない。スレッドセーフにするためにはsynchronized修飾子が必要だが、これはパフォーマンス低下を引き起こす場合がある。

Bill Pugh Singletonは上記の欠点をシンプルな書き方で解決が可能である。欠点としてはなぜスレッドセーフになるのか説明しづらい点だと思う。

参考

Java : Path.ofとPaths.ofの違い

概要

プロになるJava 12.1においてPath.ofメソッドが使われているが、Java11以前ではPaths.getメソッドを使用していた。 これらの違いについて調べてみた。

Paths.ofメソッドについて

公式リンク:docs.oracle.com Java 20現在、Paths.ofの内部実装ではPath.ofを呼び出しているだけである。 そのため、どちらを使っても本質的には変わらないと思われる。

導入の背景

Javaにofメソッドが追加された際の一貫性を保つために、Path.ofが追加された。

どちらを使うべきか

本メソッドの本質は与えた文字列をそのままPathに変換することなので、単純な変換を表すofメソッドの方が文脈上わかりやすいと思う。 当然、どちらを使ってもよい

その他

参考のStackOver Flowの参照リンクにおいてJava13においてはPaths.getは将来的に非推奨になる旨が書かれていた。 Java20のドキュメントについて本件を確認してみたが、特にそのような記述はなかった。 とはいえ、レガシーなコードでない限りPath.ofを使っていきたいと思う。

参考

Javaの Paths.get と Path.of はどちらを使っていますか?

java - Paths.get vs Path.of - Stack Overflow

wezterm, exit_behavior="CloseOnCleanExit"の対処法

発端

最近、Rust製ターミナルエミュレータのweztermを利用しているが、

CTRL-Dでターミナルを閉じることができない現象に見舞われた。

この状況が発生すると操作を受け付けなくなり、自分でターミナルを閉じるする他なくなる。

以下、その際に表示される表示エラー文


    Process "/usr/bin/zsh" in domain "local" didn't exit celanly Exited with code 127.
    This message is shown because exit_behavior="CloseOnCleanExit"
    

当初はzsh側の問題化と勘違いしていたが、wezterm側の設定であることが判明した。

原因

weztermのexit_behaviorと呼ばれる設定によるもの。下記参照

exit_behavior - Wez's Terminal Emulator

本現象は例えばタイポで存在しないコマンドを実行するなど、

コマンドの終了コードが0以外の際にCTLR-DでEOFを送ると発生する。

解説

exit_behaviorは文字通り、shell終了時のターミナルの制御設定である。

デフォルトの設定値が"CloseOnCleanExit"となっているが、これは下記のような制御を行う。

  • 正常終了(0)なら端末を閉じる(Close)
  • それ以外なら端末を開いたままにする(Hold)

weztermはCTRL-D実行時に最後のコマンドのステータスコードを読みに行くため、

例えばタイポ時の終了コード:127を参照してHold制御を実行して固まってしまう。

対処法

wezterm.lua等に以下の記述を追加する。本設定により確実にターミナルを終了できる。

exit_behavior = "Close"

なお2022/6/10現在、nightly build限定ではあるが

特定exitコードのみホワイトリスト化する場合はclean_exit_codesを利用可能なようだ。

clean_exit_codes - Wez's Terminal Emulator

 

Arch Linuxのインストール方法、解説

背景

C++開発用に古いノートPCにubuntuを入れていたが、最近動作が遅くなってきてしまった。
そこで以前より興味のあったArch Linuxを導入した。その際の備忘録を残す。
基本的には英語版の公式wikiを見つつ、各種ブログを見ながらインストールした。

環境

ThinkPad X230

事前準備

インストールメディアの作成

Arch Linuxのイメージを入手し、署名による改竄検証を行う。
インストールメディア作成に当たっては、rufusを用いてUSBに書き込んだ。

UEFIの設定

大半の解説サイトがUEFIモードでのインストールを前提としていたため、
下記のUEFI設定をチェックする。
特に自分の環境はUEFI/LegacyがBIOS優先になっていたのため、当初はUEFIに入れなかった。

  • secure boot
  • fast boot
  • UEFI/Legacy boot(BIOS/UEFIどちらでbootするか)

USB boot

ここからはUSB起動後の話。

ログ記録

scriptコマンドによって、実行したコマンドが記録される。
オプション無しならカレントディレクトリにtypescriptと呼ばれるログができる。
インストールには直接関係ないが、念のため。

script

キーボードレイアウト

US配列を使用しているので割愛

boot modeチェック

以下のディレクトリがあればUEFI。無ければBIOS。起動画面でも判別可能。

ls /sys/firmware/efi/efivars

ネットワーク接続

無線wifiのため、iwctlコマンドで接続。
念のためping確認もしておく。
余談だがこれはusbには入っているコマンドであり、
インストールされるArch Linuxには入っていない。
別途ネットワーク関連のコマンドのインストールが必要(2敗)。

iwctl
[iwd] device list
[iwd] station hoge_device scan
[iwd] station hoge_device get-networks
[iwd] station hoge_device connect hoge_SSID
[iwd] exit
ping archlinux.org

system clockの更新

timedatectl set-ntp true

パーテーション

良く知らなかったが、linuxはパーテーション分割するのが常識らしい。公式WikiUEFI with GPTを参考にした。

  • boot 起動に必要
  • root 各種システムファイル
  • swqp メモリ領域の拡張。一応作った

diskの確認

まずdiskを確認する。

fdisk -l

fdisk対話モード開始

上記で確認した対象のdiskを対象に操作を行う。ここからはfdiskの対話モードで設定

fdisk /dev/sda

パーテーションテーブル

パーテーションテーブルはGPTが良さげなのでそれにする。コマンドはg

g

パーテーションの追加

それぞれの容量はこの以下とした。swap領域の目安は8GB RAMの場合は4GB~10GBらしいので最大値にしておいた。

  • boot /dev/sda1 512MB
  • root /dev/sda2 残り全部
  • swap /dev/sda3 10GB

nでパーテーション追加。以下のコマンドは割愛。領域は+512Mや-10GBなど相対指定可能。

n

パーテーションタイプ

tでパーテーションタイプ設定。
その後対象パーテーションとタイプを指定する。
タイプの番号はboot領域はuefi(1)、swap領域はswqp(19)

t

設定書き込み

wで設定を書き込み、対話モード終了。qだと書き込まず終了

w

フォーマット

mkfsコマンドでフォーマットしていく。
wiki曰くEFI system partitionはfat32が推奨らしい。
rootはext4が一般的。
サイトによってはmkfsを利用していたが、
wikiに倣ってmkswapを用いた。

mkfs.fat -F32 /dev/sda1
mkfs.ext4 /dev/sda2
mkswqp /dev/sda3

マウント

これもwikiに倣いEFI system partitionは/mnt/bootに、rootは/mntにマウントする。
/mnt/bootの作成を忘れないこと。swapはswaponコマンドで有効化するらしい。

mount /dev/sda2 /mnt
mkdir /mnt/boot
mount /dev/sda1 /mnt/boot
swapon /dev/sda3

--追記(2021/11/5) /mnt/bootの作成とマウントは/mntのマウント後でないとまずいようだ

ミラーサイトの更新

/etc/pacman.d/mirrorlistにミラーサーバの設定が書いてあり、日本サーバーが優先になるように設定する。
本来ならば、上記のファイルの順番を変えるだけで対応可能である。
が、自分の環境では日本サーバーの設定がなかったため、日本サーバー情報を取得した。
以下は日本サーバにおいて24時間以内にhttpsで接続されたサーバ情報を通信速度順にソートをかけて保存するコマンド。

reflector --country 'Japan' --age 24 --protocol https --sort rate --save /etc/pacman.d/mirrorlist

パッケージのインストール

いよいよパッケージインストール。最低でも以下が必要。

また、このタイミングで以下のパッケージも入れておいた方が良い。
入れ忘れたパッケージがあっても、
後述のchroot後にpacmanコマンドで入れてもよい。

  • テキストeditor : vim/emacs/nano等
  • ネットワーク関連 : iw/dhcpなど

自分の環境では以下とした

pacstrap /mnt base linux linux-firmware vim iw wpa_supplicant dhcpcd

Note

上記以外のパッケージはOSインストール後でも間に合う。
archlinux的考え方で行けば上記すら不要かもしれないが、
以下の理由で先に導入した。
linux力が高ければ不要だと思う。
* テキストeditorがなく、OSインストール後のシステムファイルを変更できなかった。
* ネットワークコマンドの使い方がわからず、パッケージ更新を行えなかった。

fstabの書き出し

linuxのファイルマウントの定義のことらしい。
UUIDと呼ばれる一意に定まるIDを用いてパーテーションをマウントする設定を書き出す。

genfstab -U /mnt >> /mnt/etc/fstab

なお、UUIDは以下のコマンドで確認可能。/mnt/etc/fstabと比較するのも良いだろう。

lsblk -f

ルートディレクトリの変更

ルートディレクトリを変更することによってインストールしたシステムを操作していく、のだと思う。

arch-chroot /mnt

timezoneの設定

ln -sf /usr/share/zoninfo/Asia/Tokyo /etc/localtime
hwclock --systohc --utc

localeの設定

/etc/locale.genのen-US.UTF-8 UTF-8とja_JP.UTF-8 UTF-8をアンコメントする。

en_US.UTF-8 UTF-8
ja_JP.UTF-8 UTF-8

次のコマンドを実行し、設定を反映

locale-gen

また、以下の設定ファイルを作成

LANG=en_US.UTF-8

ホストネームの設定

以下で設定。

hogehoge

また、ホスト名とipアドレス設定ファイルを作成する

127.0.0.1 localhost
::1       localhost
127.0.1.1 hogehoge.localdomain hogehoge

initramfsの設定

RAM Filesystem、つまり、メモリ上のファイルシステムのこと。
/etc/mkinitcpio.confをいじって下記コマンドで反映させる。基本的には不要らしいが、一応実行しておいた。

mkinitcpio -P

rootのパスワード設定

passwd

ブートローダ

ブートローダーの設定も必要らしい。
今回はwikiを見て良さげだったのでGRUBを採用した。
UEFI systemの場合、efibootmgrのインストールも必要。

pacman -S grub
pacman -S efibootmgr

efi-directoryはEFIをマウントした、/bootを指定
bootloader-idはUEFIから確認できる名前っぽい。

grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=hoge

以下のコマンドでconfig fileを作成する。
-oは出力する設定ファイル名を指定するオプション。

grub-mkconfig -o /boot/grub/grub.cfg

マイクロコード

マイクロコードはプロセッサのファームウェアと同義らしい。 intel製CPUなので、intel-ucodeをインストール

pacman -S intel-ucode

上記のloadタイミングは色々選べるようだが、 GRUBにより起動時のloadを選択した。

grub-mkconfig -o /boot/grub/grub.cfg

終了処理とログの書き出し

ルートから抜け出す。

exit

次のexitでscriptを終了し、ログを終了する。

exit

usb内のデータは削除されるので、これをbootに移す

cp typescript /mnt

再起動する。自動でアンマウントされるので、マウント処理は割愛。

Hello Arch Linux

起動することを確認する。

その他

後日、ArchLinuxインストール後の処理を書く予定

参考

本家wikiが日本語版・英語版共に役に立った。内容は英語>日本語で参考にした。
ブログも大変参考になった。 

テンプレートクラス内のusingによるエイリアス宣言について

背景

以下のようなテンプレートクラスにおいて、
使用する型を短縮したい場合がある。
例えばコンテナ型を用いる場合、テンプレートが入れ子になって
ごちゃごちゃして見づらい。

template <class T, class K>
class ScoreTable
{
public:
  // 型名が長すぎる
  using Dmap = std::unordered_map<T, std::unordered_map<K, int>>;
  int GetScore(T row, K col);
  void SetScore(T row, K col, int val);
  void SetDmap(Dmap &dmap);

private:
  std::unordered_map<T, std::unordered_map<K, int>>dmap_;
};

これをこうしたい。

// どこかで以下を宣言
using Dmap = std::unordered_map<T, std::unordered_map<K, int>>;
// 型がすっきりに
template <class T, class K>
class ScoreTable
{
public:
  int GetScore(T row, K col);
  void SetScore(T row, K col, int val);
  void SetDmap(Dmap &dmap);

private:
  Dmap dmap_;
};

問題点

テンプレートクラスで使うためにはグローバル空間で
using宣言はできない?と考えclass内でusing宣言にする作戦。
ただ、void SedDmap()関数は引数に Dmap型を使うため、
外部にDmap型を知らせるのかわからなかった。

結論

classのpublicに型エイリアスを宣言する。
エイリアスをクラスから参照できることを知らなかった。

適当にクラス内で宣言して使っていたが、
privateになっていることが頭から抜けていた。

ソースコード

double_map.h

#ifndef DOUBLE_MAP_H_
#define DOUBLE_MAP_H_

#include <unordered_map>

template <class T, class K>
class ScoreTable
{
public:
  using Dmap = std::unordered_map<T, std::unordered_map<K, int>>;
  int GetScore(T row, K col);
  void SetScore(T row, K col, int val);
  void SetDmap(Dmap &dmap);

private:
  Dmap dmap_;
};

template <class T, class K>
int ScoreTable<T, K>::GetScore(T row, K col)
{
  return dmap_.at(row).at(col);
}

template <class T, class K>
void ScoreTable<T, K>::SetScore(T row, K col, int val)
{
  dmap_.insert({row, {}});
  dmap_[row].insert({col, val});
}

template <class T, class K>
void ScoreTable<T, K>::SetDmap(Dmap &dmap)
{
  dmap_ = std::move(dmap);
}
#endif

上記クラスを使う側

double_map.cc

#include <iostream>
#include <string>
#include "double_map.h"

int main()
{
  using Stbl = ScoreTable<std::string, std::string>;
  Stbl dmap;
  Stbl::Dmap set_map = {{"Tom", {{"english", 95}, {"math", 75}}},
                        {"Jaws", {{"english", 63}, {"math", 52}}}};
  dmap.SetDmap(set_map);
  std::string student = "Tom";
  std::string subject = "math";
  std::cout << student << " : " << subject << " : score : ";
  std::cout << dmap.GetScore(student, subject) << std::endl;

  return 0;
}

出力

Tom : math : score :75

using template type parameter ‘T’ after ‘class’ エラーについて

発端

C++テンプレートの勉強をしていた時の備忘録
試しに二次元連想配列(キーを二つ持つ配列)をテンプレートクラスを
使ったところ、コンパイルエラーが発生した
以下コンパイルエラーの一部

double_map.h:18:21: error: using template type parameter ‘T’ after ‘class’
   18 | int DoubleMap<class T, class K>::GetValue(T row, K col)

原因

メンバ関数の定義の際に、'class' をつけてしまっていた。
こんなミスだったなんて・・・

template <class T, class K>
int DoubleMap<class T, class K>::GetValue(T row, K col)
{
  return map_.at(row).at(col);
}

上記のint DoubleMap< class T, class K>部分
定義の際にはint DoubleMap< T, k>で良い。

template <class T, class K>
int DoubleMap<T, K>::GetValue(T row, K col)
{
  return map_.at(row).at(col);
}

結論

エラーはよく読もう

ソースコード

#ifndef DOUBLE_MAP_H_
#define DOUBLE_MAP_H_

#include <unordered_map>

template <class T, class K>
class DoubleMap
{
public:
  int GetValue(T row, K col);
  void SetValue(T row, K col, int val);

private:
  std::unordered_map<T, std::unordered_map<K, int>> map_;
};

template <class T, class K>
int DoubleMap<class T, class  K>::GetValue(T row, K col)
{
  return map_.at(row).at(col);
}

template <class T, class K>
void DoubleMap<class T, class K>::SetValue(T row, K col, int val)
{
  map_.insert({row, {}});
  map_[row].insert({col, val});
}

#endif

正しくはこちら

#ifndef DOUBLE_MAP_H_
#define DOUBLE_MAP_H_

#include <unordered_map>

template <class T, class K>
class DoubleMap
{
public:
  int GetValue(T row, K col);
  void SetValue(T row, K col, int val);

private:
  std::unordered_map<T, std::unordered_map<K, int>> map_;
};

template <class T, class K>
int DoubleMap<T, K>::GetValue(T row, K col)
{
  return map_.at(row).at(col);
}

template <class T, class K>
void DoubleMap<T, K>::SetValue(T row, K col, int val)
{
  map_.insert({row, {}});
  map_[row].insert({col, val});
}

#endif