ぷろみん

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

修正: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();
}

C++のAbstractFactoryはvirtualを使わない

抽象化ができてなかったので修正しました。

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

継承

C++では出来る限り継承は使わない方が良いです。
理由としては結びつきが強すぎるからです。
メンバに持って目的の挙動を達成できるのなら、そちらの方が優れています。

Mixin

出来る限り使わない、ではどんな時なら使って良いのか。その使って良い時がMixinとして利用する場合です。
特定のオブジェクトに特定の性質を加える場合、幅広い選択肢があり、再利用性に優れます。また、役割が明確化します。

AbstractFactory

namespace torini {

    class Toy {
    public:
        void Play() const {}
    };
    
    class Game {
    public:
        void Play() const {}
    };

    template<class Product>
    class AbstractFactory {
    public:
        Product Create() const {
            return Product();
        }
    };
    
    template<class ...Types>
    class Factory : public AbstractFactory<Types>... {};

}

int main() {
    auto factory = torini::Factory<torini::Toy, torini::Game>();
    
    auto toy = factory.torini::AbstractFactory<torini::Toy>::Create();
    toy.Play();
    
    auto game = factory.torini::AbstractFactory<torini::Game>::Create();
    game.Play();
}

factory.torini::AbstractFactory<torini::Toy>::Create();が辛い感じなのでfactory.Create<torini::Toy>()と書けてかつfactoryで定義したCreateのみ使えるような方法が分かる方がいたら是非とも教えてくださいな。

ポインタよりも参照を使おう

あんまり関係の無い所

int& aint &aのどちらで書くでしょうか?
私はint &aの方で書きます。

    // int i;
    // int *ip;
    // int *const ipc = &i;
    // int &ir = i;
    int i, *ip, *const ipc = &i, &ir = i;
    // const int ci = 0;
    // const int *cip;
    // const int *const cipc = nullptr;
    // const int &cir = i;
    const int ci = 0, *cip, *const cipc = nullptr, &cir = i;
    // int constでも良い

上記の様に最初のint&の関連性が低い為です。

    int a = 0;

    // 間違えそう!
    // int &b = a;
    // int c = a;
    int& b = a, c = a;

特性に注目する

int main(){
    int i, *ip, *const ipc = &i, &ir = i;
    const int ci = 0, *cip, *const cipc = nullptr, &cir = i;
    
    // mutable
    i = 0;
    ip = &i;
    cip = &i;
    
    // writeable
    *ip = 0;
    *ipc = 0;
    ir = 0;
}

mutableな変数は自身の値を再代入できます。
writeableな変数は他の変数の値を書き換える事ができます。

ここで紛らわしいのがiciです。
imutableなばかりに先頭にconstを付けた場合、変数の変更ができないように考えてしまいます。

先頭にconstを付ける事によってwriteableでは無くなり、*constを付ける事によってmutableでは無くなると考えると分かりやすいです。

参照は分かりやすい

この特性で参照を考えると参照は非常に分かりやすいです。
参照はmutableではありません。
int &irwriteableです。
const int &cirwriteableではありません。

分かりやすい!

push_backよりもemplace_backを使おう

emplace

push_backでは無駄なコストが発生してしまいます。
そこで、直接コンストラクトできるemplace_backがC++11から追加されました。

#include<vector>

namespace torini{

struct Data{
    Data(int, int, int){}
};

}

int main(){
    std::vector<torini::Data> v;
    // 無駄にコンストラクトしてしまう
    v.push_back(torini::Data(0, 1, 2));

    // 綺麗だし、コンストラクトコストが発生しない
    v.emplace_back(0, 1, 2);
}

使い分け

全部emplace_backでも良い気はしますが、あえて明示的にする為にコンストラクタが存在する要素の場合のみemplace_backを使う等が良い気がします。

mapよりもunordered_mapを使おう

unorderedとは

要するにハッシュです。検索が早くなるので基本的にはunordered_mapを使いましょう。
mapはorderedつまり常にソートされた状態で保持されますが、これが有効に働くことは連想配列的な使い方ではまず無いでしょう。

でもkeyに複雑な値が来る場合は辛い

unordered_mapではkey値からハッシュ値が計算できないと機能しません。
その為、key値に複雑な型が来る場合はハッシュの計算仕方を教えてあげなければいけません。

#include<iostream>
#include<unordered_map>
#include<functional>

namespace torini{
    struct Key{
        int value = 0;
        
        // keyは等号を処理できなければ同じか判断できません
        bool operator==(const Key& other) const{
            return value == other.value;
        }
    };
    struct Hash{
        std::size_t operator()(const Key& key) const{
            // ここに良い感じのハッシュ関数を書きましょう
            return 0;
        }
    };
    using Map = std::unordered_map<Key, int, Hash>;
}

int main(){
    torini::Map map;
    // map.insert(std::make_pair(torini::Key(), 1));よりもオススメ
    map.emplace(torini::Key(), 1);

    // 1が出力される
    std::cout << map.find(torini::Key())->second;
}

妥協策

定義されてあるようなプリミティブな型はunordered_mapを使い、他はmapを使う。 mapで速度的な負担が計測されたらHashを書く。で良いんじゃないですかね!

探して見つかった場合と見つからなかった場合の処理

C++14の機能を利用しているのでコンパイラによってはビルドが通らないです。

ありがち

処理の分割がいまいちで、コードを読む時に分かりにくいです。
また、for内で読み込みと書き込みを同時に行っているのは良くない書き方の兆候です。

#include<vector>
#include<utility>

int main(){
    std::vector<std::pair<bool, int>> v;
    // スコープが長いフラグ変数は怖い
    bool find = false;
    for(auto &&item: v){
        if(item.first){
            // ここでうっかり要素の追加とかすると危険な可能性がある
            item.second = 5;
            find = true;
            break;
        }
    }
    
    if(find){
        // ...
    } else {
        // ...
    }
}

標準ライブラリ

こういう処理はfind_ifを使うと良い感じに書けます。

#include<vector>
#include<utility>
#include<algorithm>

int main(){
    std::vector<std::pair<bool, int>> v;
    auto result = std::find_if(v.begin(), v.end(), [](const auto &item){
        return item.first;
    });
    
    // ちょっとここがダサい
    if(result != v.end()){
        result->second = 5;
        // ...
    } else {
        // ...
    }
}

linq的なアプローチ

何となく意図が掴めれば良いという感じで書いたコードなのでちゃんと動きません。

しっかりとしたlinq的な挙動が良いのならcpplinqを参照してください。
LINQ for C++ - Home

要するに対象を絞った後で処理をするテンプレートの様なものです。

このままだと上手く代入できません。
この辺の問題でしょうか?どちらにしろ理解不足です。
c++ - std::optional specialization for reference types - Stack Overflow

#include<vector>
#include<utility>
#include<experimental/optional>

template<class Container, class Func>
auto Where(Container &v, Func func)
// optionalにはautoが効かない。ぐぬぬ
-> std::experimental::optional<typename Container::value_type>
{
    for(auto &&item: v){
        if(func(item)){
            return item;
        }
    }
    return std::experimental::nullopt;
}

int main(){
    std::vector<std::pair<bool, int>> v;
    auto target = Where(v, [](auto&& item){ return item.first; });
    
    if(target){
        // ここでの代入が上手く機能しないのでもう少し考える必要がある
        (*target).second = 5;
        // ...
    } else {
        // ...
    }
}

C++14の簡単に利用できる便利な機能

ラムダ引数の自動化

int main(){
    std::vector<int> v;
    // ラムダの引数にautoが使える
    std::for_each(v.begin(), v.end(), [](auto i){
        std::cout << i;
    });
}

ラムダ引数が複雑になった場合記述するのは冗長です。
その冗長性が省略できるのは簡単でかつ便利です。