としおの読書生活

田舎に住む社会人の読書記録を綴ります。 主に小説や新書の内容紹介と感想を書きます。 読書の他にもワイン、紅茶、パソコン関係などの趣味を詰め込んだブログにしたいです。

カテゴリ: C++

3753817_s


本記事では、デザインパターンの名著である結城浩さんの『Javaで学ぶデザインパターン入門』を参考にアダプターパターン(Adapter)をC++で実装してみました。



アダプターパターン(Adapter)とは


アダプターパターンとは、「すでに提供されているもの」と「必要なもの」の間にある「ずれ」を埋めて再利用するためのデザインパターンです。

提供されたAPIなどを手直しするために使われます。

アダプターパターンには、以下の2種類の実装方法があります。

  • クラスによるAdapterパターン(継承を使ったもの)
  • インスタンスによるAdapterパターン(委譲を使ったもの)



クラスによるAdapterパターン(継承を使ったもの)


継承を使ったアダプターパターンは以下のようなクラス図になります。


アダプターパターン_継承



継承のAdapterパターンは、Adaptee役のメソッドがほぼそのまま使えそうなときに使われます。


Target(対象)


今必要となっているメソッドを定める役で、インタフェースとなっています。

C++では、二重継承を非推奨としているため、継承を使ったアダプターパターンでは、Targetは省略されることが多いです。


Client(依頼者)


Target役(省略する際はAdapter役)のメソッドを使って仕事をする役です。

main関数のようなAPIを呼び出すやつだと思ってもらえば大丈夫です。


Adaptee(適合される側)


すでに用意しているメソッドをもっている役です。要するに使いたいAPIを持っているクラスのことですね。

Target役が使いたいAPIがAdaptee役ですでに実装していたら、Adapter役は必要ないんですけどね…。


Adapter


Adapterパターンの主役です。Adaptee役のメソッドを使用して、Targetが求めているメソッドを作り出すやくですね。



インスタンスによるAdapterパターン(委譲を使ったもの)


委譲を使ったアダプターパターンは以下のようなクラス図になります。


アダプターパターン_委譲


委譲を使ったAdapterパターンは、Adaptee役のメソッドを呼び出す前に手を加えたいことが多い場合に有効です。

Adaptee役のメソッドをそのまま呼び出したい場合は、メソッドを再定義する必要があり、コード量が増えるというデメリットもあります。

C++では多重継承(インタフェースと親クラスの同時継承)が推奨されていないこともあり、委譲を使ったAdapterパターンのほうがよくつかわれるような気がします。



C++で継承をつかったアダプターパターンを実装


今回は、与えられた文字列を

(Hello)
*Hello*

のように表示する簡単なものをAdapterパターンを使用して実装していきます。

クラス構成は以下の通りです。


アダプターパターン_サンプル1



二重継承をしないようにTargetクラスを省略した実装にしています。


Bannerクラス


Bannerクラスは、事前に用意されていたAPIクラスだと思ってください。

■ヘッダファイル

#ifndef BANNER_H
#define BANNER_H

#include <iostream>

class Banner{
public:
    Banner(std::string string);
    
    virtual ~Banner();

    void showWithParen();

    void showWithAster();

private:
    std::string m_string;
};
#endif // BANNER_H


■ソースファイル

#include "Banner.h"

Banner::Banner(std::string string)
m_string(string
{

}

Banner::~Banner(){}

void Banner::showWithParen(){
    std::cout << "(" << m_string << ")" << std::endl;
}

void Banner::showWithAster(){
    std::cout << "*" << m_string << "*" << std::endl;
}



PrintBannerクラス


PrintBannerクラスがアダプターの役目を果たします。

今回はm単純な実装のためBannerクラスのAPIをそのまま呼び出すだけになります。

■ヘッダファイル

#ifndef PRINTBANNER_H
#define PRINTBANNER_H

#include "Banner.h"

class PrintBanner : public Banner{
public:
    PrintBanner(std::string string);

    ~PrintBanner();

    void printWeak();
    
    void printStrong();

};
#endif // PRINTBANNER_H


■ソースファイル

#include "PrintBanner.h"

PrintBanner::PrintBanner(std::string string)
Banner(string)
{

}

PrintBanner::~PrintBanner(){}

void PrintBanner::printWeak(){
    showWithParen();
}

void PrintBanner::printStrong(){
    showWithAster();
}



main関数


ここまで作ってきたAdapter役のPrintBannerクラスを使って、"Adapter Sample"という文字列を括弧付きと*ではさんで表示します。

■ソースファイル

#include "PrintBanner.h"

int main(void){
    PrintBanner printBanner("Adapter Sample");
    
    printBanner.printWeak();
    printBanner.printStrong();

    return 0;
}


■実行結果

(Adapter Sample)
*Adapter Sample*






C++で委譲をつかったアダプターパターンを実装


委譲を使ったアダプターパターンのサンプルプログラムのクラス構成は以下の通りです。


アダプターパターン_サンプル2


Bannerクラス


■ヘッダファイル

#ifndef BANNER_H
#define BANNER_H

#include <iostream>

class Banner{
public:
    Banner(std::string string);
    
    virtual ~Banner();

    void showWithParen();

    void showWithAster();

private:
    std::string m_string;
};
#endif // BANNER_H


■ソースファイル

#include "Banner.h"

Banner::Banner(std::string string)
m_string(string
{

}

Banner::~Banner(){}

void Banner::showWithParen(){
    std::cout << "(" << m_string << ")" << std::endl;
}

void Banner::showWithAster(){
    std::cout << "*" << m_string << "*" << std::endl;
}



Printクラス


JavaでいうインタフェースはC++では抽象クラスとして実装していきます。

■ヘッダファイル

#ifndef PRINT_H
#define PRINT_H

class Print{
protected:
    virtual ~Print(){};

    virtual void printWeak()=0;
    
    virtual void printStrong()=0;
};
#endif // PRINT_H



PrintBannerクラス


■ヘッダファイル

#ifndef PRINTBANNER_H
#define PRINTBANNER_H

#include <iostream>
#include "Print.h"

class Banner;

class PrintBanner : public Print{
public:
    PrintBanner(std::string string);

    virtual ~PrintBanner();

    void printWeak() override;
    
    void printStrong() override;

private:
    Banner *m_Banner;

};
#endif // PRINTBANNER_H



■ソースファイル

#include "PrintBanner.h"
#include "Banner.h"
#include "Print.h"

PrintBanner::PrintBanner(std::string string)
{
    m_Banner = new Banner(string);
}

PrintBanner::~PrintBanner(){
    delete m_Banner;
}

void PrintBanner::printWeak(){
    m_Banner->showWithParen();
}

void PrintBanner::printStrong(){
    m_Banner->showWithAster();
}



main関数


■ソースファイル

#include "PrintBanner.h"

int main(void){
    PrintBanner printBanner("Adapter Sample");
    
    printBanner.printWeak();
    printBanner.printStrong();

    return 0;
}


■実行結果

(Adapter Sample)
*Adapter Sample*




まとめ


個人開発ではなく、会社などで集団で開発する場合は、いきなりAPIの使用が変更することが多々あります。

いきなりの変更でも困らないようにアダプターパターンを使って、修正箇所ができる限り少なくなるようなプログラムを作っていきましょう。







647498_s


windowsでtimeGetTime関数を使用して時間計測のプログラムを作ろうとしたろころ、コンパイルの段階で

undefined reference to `__imp_timeGetTime'

というエラーが現れて少し苦戦したので対処法を残します。



コンパイルしたいコード


コンパイルしたいと思ったコードは、timeGettime関数を使って実行時間を計測するコードです。
ソースファイル名はsample.cppとします。

#include <iostream>
#include <windows.h>

int main(void){
    DWORD start_tm = timeGetTime();

    for(int i=0i<1000; ++i){
        for(int j = 0j<10000000; ++j){}
    }

    DWORD end_tm = timeGetTime();
    std::cout << end_tm - start_tm << std::endl

    return 0;
}



コンパイル失敗


何も考えずにコンパイルすると…

g++ -o sample sample.cpp

以下のメッセージがでて、そんなもの定義していないぞと怒られてしまいます。

undefined reference to `__imp_timeGetTime'




`__imp_timeGetTime'の対処法


エラーについて調べてみるとtimeGetTime関数を使用するためにはwinmm.libをリンクする必要があるということが分かりました。

なのでコンパイルコマンドを以下に直すと

g++ -o sample sample.cpp -lwinmm

コンパイルが成功しました。



まとめ


windows.hで定義しているAPIを使用するためには、必要に応じてライブラリをリンクする必要があるということが分かりました。

普段Visual Studioばかりつかっていたら、そのあたりをそこまで意識しなくていいので気をつけなければなりませんね…。




3753817_s


本記事では、デザインパターンの名著である結城浩さんの『Javaで学ぶデザインパターン入門』を参考にIteratorパターンをC++で実装してみました。

C++のSTLでは、すでにイテレータは実装されているので、改めて実装することはないのですが、今回は設計思想を学ぶために実装していきます。




イテレータパターンとは


イテレータパターンとは、何かが同じようなものがたくさん集まっているときに、それを順番に指し示していき全体をスキャンする処理を行うためのものです。

例えば、本棚から本の名前を順番に表示するといったときに使うことができます。

ちなみにイテレータ(Iterator)とは、日本語で繰り返すという意味です。



イテレータパターンの登場人物


イテレータパターンは以下のようなクラス構成になっています。



イテレータパターン


Iterator


要素を順番にスキャンしていくAPIを定める役。

次の要素を得るためのhasNextメソッドや次の要素を得るためのnextメソッドなどを定めます。


ConcreateIterator


Iterator役が定めたAPIを実際に実装する役。

この役はスキャンをするのに必要な情報をもっている必要があります。


Aggregate


Iterator役をCreateするためのAPIを定める役。

今回の場合、IteratorがIteratorを作成する、メンバ関数になります。


ConcreateAggregate


Aggregate役が定めたAPIを実際に実装する役。

ConcreateAggregateが自信をサーチしてもらうために必要な具体的なIteratorであるConcreateIteratorを作成します。





C++による実装


今回は、本棚から本の名前を順番に表示するといったイテレータパターンを使ったサンプルプログラムを実装していきます。

クラス構成は以下の通りです。


イテレータパターン (2)



Aggregateインタフェース


JavaでいうインタフェースはC++では抽象クラスとして実装していきます。

#ifndef AGGREGATE_H
#define AGGREGATE_H

#include <iostream>

class Iterator;

class Aggregate{
public:
    virtual Iterator* iterator() = 0;

    virtual ~Aggregate(){};
};
#endif // AGGREGATE_H



Iteratorインタフェース


こちらもAggregateインタフェースと同様に抽象クラスでの実装です。

#ifndef ITERATOR_H
#define ITERATOR_H

class Book;

class Iterator{
public:
    Iterator(){};

    virtual bool hasNext() = 0;

    virtual Book next() = 0;

    virtual ~Iterator(){};
};
#endif // ITERATOR



Bookクラス


Bookクラスは本を表すクラスです。

今回の場合できることは、本の名前を設定することと取得することだけです。

■ヘッダーファイル

#ifndef BOOK_H
#define BOOK_H

#include <iostream>

class Book{
public:
    Book();

    virtual ~Book();

    void setName(std::string name);

    std::string getName();

private:
    std::string m_name;
};
#endif // BOOK_H


■ソースファイル

#include "Book.h"

Book::Book()
m_name("")
{
    
}

Book::~Book(){
}

void Book::setName(std::string name){
    m_name = name;
}

std::string Book::getName(){
    return m_name;
}



BookShelfクラス


BookShelfクラスは本棚を表すクラスです。

こちらのクラスが先ほどのBookクラスの集合体になります。

■ヘッダーファイル

#ifndef BOOKSHELF_H
#define BOOKSHELF_H

#include <iostream>
#include "Aggregate.h"

class Book;
class Iterator;

class BookShelf : public Aggregate{
public:
    BookShelf(int maxsize);

    virtual ~BookShelf();

    Book getBookAt(int index);

    void appendBook(Book book);

    int getLength();

    Iterator* iterator();

private:
    Book *m_books;
    int m_last;
};
#endif // BOOKSHELF_H


■ソースファイル

#include <iostream>
#include <unistd.h>

#include "BookShelf.h"
#include "Book.h"
#include "BookShelfIterator.h"

BookShelf::BookShelf(int maxsize)
m_last(0)
{
    m_books = new Book[maxsize];
}

BookShelf::~BookShelf(){
    delete m_books;
}

Book BookShelf::getBookAt(int index){
    return m_books[index];
}

void BookShelf::appendBook(Book book){
    m_books[m_last= book;
    m_last++;
}

int BookShelf::getLength(){
    return m_last;
}

Iterator* BookShelf::iterator(){
    return new BookShelfIterator(this);
}



BookShelfIteratorクラス


BookShekfクラスのスキャンを行うクラスです。

■ヘッダーファイル

#ifndef BOOKSHELFITERATOR_H
#define BOOKSHELFITERATOR_H

#include "Iterator.h"
#include "BookShelf.h"

//class BookShelf;
class Book;

class BookShelfIterator : public Iterator{
public:
    BookShelfIterator(BookShelf* bookShelf);

    virtual ~BookShelfIterator();

    bool hasNext() override;

    Book next() override ;

private:
    BookShelf *m_bookShelf;
    int m_index;
};
#endif // BOOKSHELFITERATOR_H


■ソースファイル

#include "BookShelfIterator.h"
#include "BookShelf.h"
#include "Book.h"

BookShelfIterator::BookShelfIterator(BookShelf *bookShelf)
m_index(0)
{
    m_bookShelf = bookShelf;  
}

BookShelfIterator::~BookShelfIterator(){

}

bool BookShelfIterator::hasNext(){
    if (m_index < m_bookShelf->getLength()){
        return true;
    }
    else{
        return false;
    }
}

Book BookShelfIterator::next(){
    Book book = m_bookShelf->getBookAt(m_index);
    m_index++;
    return book;
}


main関数


今まで作成してきたクラスを使って実際に小さな本棚を作っていきます。

■ソースファイル

#include <iostream>
#include "BookShelf.h"
#include "Book.h"
#include "BookShelfIterator.h"

int main(void){
    BookShelf bookShelf(4);

    Book book1;
    book1.setName("Around the World in 80 days");
    bookShelf.appendBook(book1);

    Book book2;
    book2.setName("Bible");
    bookShelf.appendBook(book2);

    Book book3;
    book3.setName("Cinderella");
    bookShelf.appendBook(book3);

    Book book4;
    book4.setName("Daddy-Long-Legs");
    bookShelf.appendBook(book4);

    Iterator *it = bookShelf.iterator();

    while(it->hasNext()){
        Book book = it->next();
        std::cout << book.getName() << std::endl;
    }

    delete it;

    return 0;
}



■実行結果


Around the World in 80 days
Bible
Cinderella
Daddy-Long-Legs



まとめ


C++では既存の実装があるため、実装する必要のないイテレータパターンを作成することでイテレータの動きが以前より分かるようになりよかったです。

今後も勉強がてらデザインパターンを実装していきます。








3753817_s


本記事では入力した値以下の素数を出力するプログラムを紹介します。



素数を求めるプログラム


素数とは1と自分以外に約数を持たない数のことです。

2, 3, 5, 7... などが素数になります。

ある値が素数かどうかプログラムで出力するには下記の関数のように、
「2 ~ ある値 - 1」の数までで割り切ることができる数がなければ素数であると判定することができます。

#include <iostream>

using namespace std;

bool isPrimeNum(int num){
    for (int i = 2; i < num; ++i){
        if (num % i == 0){
            return false;
        }
    }
    return true;
}

上記の関数を使い2~入力値まで順番に素数かどうか判定することで、入力した値以下の素数を全て出力することができます。

int main(void){
    int num;
    cin >> num;

    for (int i = 2; i <= num; ++i){
        if (isPrimeNum(i)){
            cout << i << endl;
        }
    }

    return 0;
}





プログラムを高速化する


先ほど紹介したプログラムは計算量がで効率が良いプログラムとは言うことができません。

そこでここでは先ほどのプログラムを高速化していきます。

まずはisPrimeNum関数を最適化しています。

先ほどの
isPrimeNum関数では「2 ~ ある値 - 1」の数までで割り切ることができる数がなければ素数であると判定していましたが、ある値が素数であるかどうか判定するには「ある値未満の素数」で割ることができるか計算するだけで大丈夫です。

なのでisPrimeNum関数を以下のように書き換えてしまいましょう。


bool isPrimeNum(int num){
    static vector primeNumVec = {2};
    for (auto itr = primeNumVec.begin(); itr != primeNumVec.end(); ++itr){
        if (num % *itr == 0){
            return false;
        }
    }
    primeNumVec.push_back(num);
    return true;
}

primeNumVecというベクターに素数と判定した数をため込んで、入力値がprimeNumVecの中の値で割り切れるかどうか判定します。


これだけでも十分早くなるのですが、main関数にも一工夫加えていきます。

素数は2以外の値は全て奇数なので、for文のiを1ずつ増やしてすべての数を見るのではなく、iを2ずつ増やして奇数だけを見るようにしましょう。

int main(void){
    int num;
    cin >> num;

    cout << 2 << endl;
    
    for (int i = 3; i <= num; i+=2){
        if (isPrimeNum(i)){
            cout << i << endl;
        }
    }

    return 0;
}


これで入力した値以下の素数を出力するプログラムを高速化することができました。


最後に


本記事を通して素数を求めるプログラムの作り方が分かりましたか。

他にもプログラムに関する記事を書いていますので興味のある方はぜひ読んでみてください。







3753817_s


本記事では入力した2個の整数から最小公倍数を求めるプログラムを2パターン紹介します。




「共通して割れる数」を探して、最小公倍数を求めるプログラム


最小公倍数を求めるシンプルな実装は共通して割れる数を元に最小公倍数を求める方法です。

入力値が24と36の場合、数のように共通して割れる数がなくなるまで割っていきます。


aa


共通して割れる数がなくなったら外側の数字をかけていきます。

aa


2 × 2 × 3 × 2 × 3 = 72

これにより24と36の最小公倍数は72だと求めることができます。

これをプログラムで実装していくと以下のようになります。 


#include <iostream>

using namespace std;

int main(void){
    unsigned int a, b, tmp;
    cin >> a >> b;
    if (b > a){
        tmp = b;
        b = a;
        a = tmp;
    }

    unsigned int Lcm = 1;
    int i = 2;
    while(b > i){
        if ((a % i == 0) && (b % i == 0)){
            a /= i;
            b /= i;
            Lcm *= i;
            i = 2;
        }
        else{
            i++;
        }
    }

    Lcm = Lcm * a * b;
    cout << Lcm << endl;

    return 0;
}





最大公約数から最小公倍数を求めるプログラム


上記の方法だとどうしても計算量が多くなります。

そこでここでは、ユークリッド互除法で最大公約数を求めてから、それをもとに最小公倍数を求める方法を紹介していきます。

入力値をaとb、aとbの最大公約数をrとすると最小公倍数は以下の式で求めることができます。


最小公倍数 = a × b ÷ r



ユークリッド互除法については以下の記事で紹介しているので参考にしてください。




最大公約数から最小公倍数を求めるプログラムは以下のとおりです。

#include <iostream>

using namespace std;

int calGcd(int a, int b){
    int tmp;
    if (a < b){
        tmp = b;
        b = a;
        a = tmp;
    }

    while(b != 0){
        tmp = b;
        b = a % b;
        a = tmp;
    }

    return a;
}

int main(void){
    int a,b,gcd, lcm;
    cin >> a >> b;
    
    gcd = calGcd(a, b);
    lcm = a * b / gcd;

    cout << lcm << endl;

    return 0;
}



最後に


本記事をとおして、最小公倍数を求めるプログラムの作り方が分かりましたか。

シンプルに「共通して割れる数」をもとに最大公約数を求める方法もいいですが、ユークリッド互除法で最大公約数を求めてから最小公倍数を求めることで計算量を減らすことができます。

プログラミングになれてきましたらぜひ計算量の小さいコーディングを意識していきましょう。




↑このページのトップヘ