ぷろみん

プログラミング的な内容を扱ってます

std::vectorの正しい使い方

イテレータ無効化ルール

イテレータは容易に無効化されます。

Iterator Invalidation Rules (C++0x)

例えばvectorはコンテナサイズよりも要素が増えた場合、要素が取り除かれた場合にイテレータは無効化されます。
なので、基本的にイテレータが無効化される操作とされない操作を明確に分離して考える事で危険を減らすと良いと思います。

const性とイテレータ無効化ルール

const vectorは一切の変更を認めませんが、イテレータ無効化ルールは要素の増減が無ければ変更を許します。
const vectorは変更をコンパイルエラーにしてくれますが、イテレータ無効化ルールは要素の増減に対してエラーを出してくれません。

そこで、イテレータ無効化を避ける方法としてSTLのalgorithmを使います。
algorithmは明示的に破壊しなければイテレーション中にイテレータは無効化されない為です。

イテレーション

基本的にできる限りイテレータを触れない様にすべきです。
何故ならイテレータへの操作はうっかりした破壊が発生しやすい上に、そのミスが分かりにくいからです。

基本的にrange based forが見た目も優れているので使うべきなのですが、イテレーション中のコンテナに触れてしまう問題があるので、心配な場合はfor_eachでも良いかもしれません

std::vector<int> v;
std::for_each(v.cbegin(), v.cend(), [](int number){
    // ここだと明示的にしなければvに触れない
});

// 明示的にイテレータを破壊する
std::for_each(v.cbegin(), v.cend(), [&v](int number){
    // 正しくイテレートできなくなる
    v.push_back(number);
});

イテレーション中にコンテナを触れない認識が共有されている場合は、値を書き換えする意図があるのかを明示します。

// 値は参照のみと明示
for(const auto &item: v){
    // itemは参照のみ可
}

// 変更を行うと明示
for(auto &&item: v){
    // moveした場合コピーコストが減るので&&で受ける
}

もし、イテレータを見えるようにしつつもread onlyにしたい場合は cbegincendを使います。こっちをデフォルトで使っていく気持ちが大事です。

要素の追加

要素の追加はreserveしていなかった場合にイテレータが壊れる可能性があります。
なので、基本的に追加した場合にイテレータが壊れると考えた方が安全です。

// イテレータが壊され正常に表示されない
std::vector<int> v = {1, 2, 3, 4};
for(const auto &item: v){
    std::cout << item << std::endl;
    v.push_back(item);
}

また要素の追加の際のコンストラクトコストを考えるとemplace_backを使うべきです。

std::vector<Foo> foos;
// Foo構築後Fooのコピーコンストラクタによって渡されFooは破棄される
foos.push_back(Foo());
// 直接構築するので無駄なオブジェクト生成が行われない
foos.emplace_back();

また、追加の際にメモリの再確保が行われると大量のコピーが発生します。

クラスをSTLコンテナにいれると恐ろしい事が起こるぞ! - 神様なんて信じない僕らのために

reserveとemplaceが意識共有されていない間はshared_ptrやunique_ptrを使うと良いと思います。
コピーについて認識が薄い内はunique_ptrがmoveしないとコピーできなくて不思議に思うかもしれませんが、オーナーシップを知る良い機会だと思います。

要素の削除

// 最悪。いくらでもバグが入り込む余地があり、なおかつ分かりにくい
std::vector<int> v(10);
for(int i = 0; i < 10; ++i){
    if(v[i] == 5){
        // 範囲外アクセスしてても気付きにくい
        for(int j = i; j < 10; ++j){
            v[j] = v[j + 1];
        }
    }
}
// イテレータへの深い理解が必要なので積極的に使うべきではない
std::vector<int> v(10);
for(auto it = v.begin(); it != v.end();){
    if(*it == 5){
        // イテレーション中にvにアクセスする事は危険
        it = v.erase(it);
        // eraseの戻り値をインクリメントせずにチェックする必要があるが忘れそう
        continue;
    }
    ++it;
}

以下がオススメの記述です。
イテレータの無効化範囲が明確で間違いが少なくなります。

// 明示的に破壊しなければイテレータは壊れない
auto remove_begin = std::remove_if(v.begin(), v.end(), [](int number){
    return number == 5;
});
// コンテナの要素数を変更するメソッドはイテレータを無効化する
v.erase(remove_begin, v.end());
// これ以降はイテレータは無効化されている

サイズ管理

何度も説明に出て来た様に想定限界数を設定し、reserveする事が大事です。

しかし、データを全部読み込んでそれ以降サイズを変更しないという場合にはメモリがもったいないです。そんな時はshrink_to_fitを使いましょう。

std::vector<int> v;
// reserveは必要
v.reserve(100);
v.emplace_back();
v.emplace_back();
v.emplace_back();
// vの確保メモリがint3つ分になる
v.shrink_to_fit();

戻り値としてのstd::vector

NRVOとかmove semanticsがあるので、現代のまともなコンパイラでは参照やポインタを経由せずに返してもコストがありません。

// 戻り値を参照やポインタにしなくても良い
std::vector<int> DefaultList(){
    std::vector<int> v;
    v.reserve(2);
    v.emplace_back(1);
    v.emplace_back(2);
    return v;
}

privateコンストラクタじゃなくてDefaulted and Deleted Functionsを使おう

概要

参考
Explicitly Defaulted and Deleted Functions

privateコンストラクタによるコピー不可クラスにはいくつかの問題があります。

コピーコンストラクタをprivateにするとコンストラクタが必要になる

struct NonCopyable
{
    // 何もしない場合でもコンストラクタを明示的に宣言する必要がある。
    NonCopyable() {};
  
private:
    NonCopyable(const NonCopyable&);
    NonCopyable& operator=(const NonCopyable&);
};

空のコンストラクタとデフォルトコンストラクタでは挙動が違う

詳しくは過去記事を参照してください。
C++の初期化は分かりにくい - ぷろみん

簡単に説明すると、空のコンストラクタはメンバの値が不定値になりやすいという事です。

メンバ関数やfriend関数はprivateコピーコンストラクタにアクセスできてしまう

参照した場合宣言のみの関数なのでエラーとなってしまいます。

意図が明確では無い

privateのコピーコンストラクタの挙動を把握している人の間でしか理解できない記述はよろしくないです。

C++11のDefaulted and Deleted Functionsを使おう

struct NonCopyable
{
    NonCopyable() = default;
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
};

git for windowsからmsys gitに移行しました。

モチベーション

最近サーバを触り始めたので、ConEmuを導入してみました。
フォントや使い心地に概ね満足していたのですが、ローカルでvimを起動すると^Mが付くエラーが大量に出てしまいました。
NeoBundle関係が大体使えなくなってしまっていました。
KaoriYa版Vimgit for windowsのセットで使っていた物をそのままmsys環境に持っていってしまったのが良くなかった様子です。

^M

環境によって改行の扱いが違うのですが、改行コードの扱い方を意識していませんでした。
NeoBundleの使い方か、gitの改行を付ける仕組みか、使っているshellも問題なのか、何が原因かは分かりませんでした。

とりあえず解決策

  • msys gitsh.exeを使う
  • msys gitvim.exeを使う
  • msys gitgit.exeを使う

Visual Studio Community 2015のデフォルトテーマ変更

VS Community 2015をインストールしたら、また白くなっていたので黒に戻そうとしましたが、どこから設定するか調べるのに思ったより手こずったのでメモしときます。

Tools -> Options -> Environment -> General -> Color Theme

一番上にあるのに気付かなかった!

Fonts and Colorsとかに気をとられてしまっていました。

静的optional

モチベーション

ヘッダの依存性を減らしたい(コンパイラファイアウォールを強くしたい)
しかし、動的メモリ確保を行いたくない

おしいoptional

optionalは静的確保できるポインタの様な物です。
静的型をnullableにする為に使われます。

後でコンストラクトできるという特性から不完全型で構築できるかなと期待しますが、できません。
なぜなら静的なメモリを最初に用意する必要がある為です。
つまり、型のサイズさえ分かれば不完全型での宣言が可能になります。

実装

// optional.h
#pragma once
#include <cstring>

namespace torini{

// optionalの簡易イメージ
template<class T, int N>
class Optional{
public:
    void Construct(){
        T t;
        std::memcpy(this, &t, N);
    }
    
    T* operator ->(){
        return reinterpret_cast<T*>(this);
    }
private:
    // 通常はこれに加えて構築済みかどうかのフラグが存在する
    char storage[N];
};

}
// foo.h
#pragma once
#include <string>

namespace torini{

// 前方宣言用。なんでも良い
class Foo{
public:
    void Print() const;

private:
    std::string message_ = "hello world";
};

}
// foo.cpp
#include "foo.h"
#include <iostream>

namespace torini{

void Foo::Print() const{
        std::cout << message_ << std::endl;
}

}
// foo_size.h
#pragma once

namespace torini{

int GetFooSize();

}
// foo_size.cpp
#include "foo_size.h"
#include "foo.h"

namespace torini{

int GetFooSize(){
    // Fooのサイズを知る為にはfoo.hが必要
    return sizeof(Foo);
}

}
// main.cpp
#include "optional.h"
#include "foo_size.h"

namespace torini{

// torini::Fooを前方宣言だけする
class Foo;

}

int main(){
    // 前方宣言とサイズを取得する事により静的ストレージを確保しつつ好きなタイミングでコンストラクトできる
    torini::Optional<torini::Foo, torini::GetFooSize()> foo;

    // foo.hをincludeしないとコンストラクトはできない
    //foo.Construct();
    //foo->Print();
}

感想

面倒なので計測して問題時間が支配的になるまでは適当で良いと思います。

サイズ取得の為の関数がジェネリックじゃないのも良くないですし。

Caps LockキーをCtrlに置換する

Windowsでの話です。
ソフト入れたり大げさな事せずにできないかと調べたら丁度良い物がありました。

www.edandersen.com

Windowsキー+Rで出現する「ファイル名を指定して実行」でpowershellと打ち込んでOKをクリック

立ち上がった青い画面のウィンドウに以下をコピペしてEnter。

Start-Process powershell -verb runAs

管理者権限を確認するポップアップが出現するので許可します。
すると新しく青い画面が出現するので、新しい方へ以下をコピペしてEnter。

Set-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\Keyboard Layout" -name "Scancode Map" -Value ([byte[]](0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x1d,0x00,0x3a,0x00,0x00,0x00,0x00,0x00))

後は再起動したらCaps Lockキーの煩わしさから解放されているはずです。

複数のキーを置換する

ついでにInsertをDeleteに置き換えを追加したい場合は

0x02, 0x00, 0x00, 0x00 // エントリ数(Caps Lock -> Ctrlと終了の2つ)

0x03, 0x00, 0x00, 0x00 // Caps Lock -> CtrlとInsert -> Deleteと終了の3つ

に変更し

0x53, 0xE0, 0x52, 0xE0 // Insert -> Delete

を追加した以下のスクリプトを実行します。

Set-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\Keyboard Layout" -name "Scancode Map" -Value ([byte[]](0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x53,0xE0,0x52,0xE0,0x1d,0x00,0x3a,0x00,0x00,0x00,0x00,0x00))

参考

「Caps」と「Ctrl」の入れ替え

修正:C++のAbstractFactoryはvirtualを使わない

C++のAbstractFactoryはvirtualを使わない - ぷろみん

前回のが抽象化できてない事が発覚したので修正しました。
前回のに修正を乗っけようと思ったのですが、そうするとMixinらへんも関係無くなるので新しいのにしておきました。

抽象化

#include<iostream>
namespace torini {

    class Toy {
    public:
        void Play() const {}
    };
    
    class Game {
    public:
        void Play() const {}
    };
    
    // CRTP
    template<class Factory>
    class AbstractFactory {
    public:
        auto CreateToy() {
            return static_cast<const Factory&>(*this).template Create<Toy>();
        }
        
        auto CreateGame() {
            return static_cast<const Factory&>(*this).template Create<Game>();
        }
    };
    
    class FactoryA : public AbstractFactory<FactoryA> {
    public:
        template<class Product>
        Product Create() const {
            std::cout << "FactoryA" << std::endl;
            return Product();
        }
    };
    
    class FactoryB : public AbstractFactory<FactoryB> {
    public:
        template<class Product>
        Product Create() const {
            std::cout << "FactoryB" << std::endl;
            return Product();
        }
    };
}

int main() {
    auto factory = torini::FactoryB();
    
    auto toy = factory.CreateToy();
    toy.Play();

    auto game = factory.CreateGame();
    game.Play();
}