めあとるーむ日記帳

なんか書く

値を返してから代入する

ちょっと前にTwitterで人が死ぬバグといって流れてきたツイートがそれなりに話題になっていたと思う。

これね。出典元は 非推奨だった bool 型に対するインクリメント演算子を削除 - cpprefjp C++日本語リファレンス

これは古いCのコードをもとにしていて、かつ当時はbool型もなかったのでこうなっていたようだが、配列の要素を並べて表示し、それぞれの要素間に,を表示するといったパターンではおそらく昔はよくあったコードだと思う。


この「配列の要素を並べて、各要素間にはコンマをうって表示する」というケースに類似した処理はよくあるものだと思う。

地味に面倒なことに、この場合「最初は飛ばすがそのあとからは毎回つける」というパターンで記述することが多い。

で、その場合(下のコードはJava

boolean first = true;
for(Object obj : objList){
    if(!first){
        System.out.print(",");
    }else{
        first = false;
    }
    System.out.print(obj.toString());
}

みたいなコードが必要になったりする。

はっきり言って面倒である。

ちなみにC++では上のリンクであるように昔はインクリメントがあった。bool型(boolean型)のインクリメントはJavaにはないし、C++も今はない。

後述するがインクリメントははっきり言ってわかりにくいものなので最近だとSwiftでは方にかかわらずインクリメント自体がなくなった。

ではこのインクリメントなしで書くにはどうするか、というと、まあ上のように一つフラグを用意してあげてとなるのだが、C++では割と便利な関数ができていた。

それがstd::exchangeというので、std::exchange - cppreference.comにある。

これは「値を返してそのあと代入する」というものである。

ただこれはJavaにはない。Javaにそんな便利なものがあると思ったら大間違いだ。欲しければ作れ。という感じでこんな感じになるだろうか

public class Exchanger<T>{
    T value;
    public Exchanger(T value){
        this.value = value;
    }

    public T get(T newValue){
        T oldValue = value;
        value = newValue;
        return oldValue;
    }
}

使うときはこう

Exchanger<Boolean> first = new Exchanger<Boolean>(true);
for(Object obj : objList){
    if(!first.get(false)){
          System.out.print(",");
    }
    System.out.print(obj.toString());
}

ちょっとはキレイになった。

問題点はJavaなんでプリミティブ型が使えないこととか、Integer型のときに +1 で回せないという点あたりか。後者の問題はそのままの値を返すget()メソッドで値を取って+1したものをnewValueとしてget(T newValue)に渡す感じにするだろうか。ジェネリクスを使う以上あまり入り込んだ処理は行いたくない。

いずれにせよJavaだと面倒。ほかの言語であればだいぶ楽(C#とかならLINQ使ったりできるかもしれない)なので調べる価値はあるはず。


ところでインクリメント(と、デクリメント)について。

いわゆる++だが、先述の通りはっきり言ってややこしい。i++++iの違いなど意識していては可読性が下がるしバグの温床になりかねない。

SwiftではSwift2からインクリメントとデクリメントがなくなったし、正直forループの処理以外で使うこともほとんどないだろう。可能な限り使わないに越したことはない。

C++の場合演算子オーバーロードでは前置か後置かの判定で描き方も面倒になっている。ついでに言うと += などの書き方も正直あまりいい物ではないと思う。

省略することが良いとは限らないし、現在なら基本はすべて記述すべきだと思う。