nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

品質を向上させるために、テストクラスを記載しよう [C++]

このように記載すれば、製品のソースコードと、テストクラスのソースコードを分離できます。

#include "box.h"
#include <climits>
#include <cassert>
#include <iostream>

class Test {

public:
        Test() {
            Run();
        }
        static void Run() {
          // 正常ケース
            assert(Test(0,0,0,0));
          // 異常ケース
            assert(Test(2,2,2,100));
          // 例外がスローされるケース
            assert(Test(INT_MAX, INT_MAX, INT_MAX, INT_MAX));
        }
        static bool Test(int x, int y, int z, int r) {
          try {
                //検証したいコードをここに記載する
          } catch (std::exception& ex) {
            return false;
          }
        }
};

static const Test test;

Qt Creator4.3 トラブルバスター

自分がQt Creator4.3を使用する際に直面した問題の対処方法について記録します。

エラー(LNK1158)が発生

<症状> fatal error LNK1158: cannot run ‘rc.exe’が発生する

<解決策> C:\Program Files (x86)\Windows Kits\8.0\bin\x64 の rc.exe と rcdll.dll を C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64 にコピーする

以下のページを参考にさせていただきました。

http://motchy99.blog.fc2.com/blog-entry-97.html

LNK2005が発生

<症状> fatal error LNK2005 * mainwindow.obj:-1: エラー: LNK2005: “class QDataStream & __cdecl operator<<(class QDataStream &,class Person const &)” (??6@YAAEAVQDataStream@@AEAV0@AEBVPerson@@@Z) は既に main.obj で定義されています。

<解決策> * 多重インクルードが発生していることが問題 * 多重インクルードを回避すること

Qt SIGNALとSLOTについて

SIGNALの説明やSLOTの説明は、以下のサイトに記載してあります。 https://blog.qt.io/jp/2010/07/20/create-signals-and-slots-2/

connect 関数

  • connect(sender, SIGNAL(signal), receiver, SLOT(slot) );
    • sender : 信号が発生する部品のアドレスを渡す
    • SIGNAL(signal) : signal に信号とする関数を渡す
    • receiver : 信号を受け取る部品のアドレスを渡す
    • SLOT(slot) : 信号を受け取った際に呼び出す関数を渡す

connect関数の使い方

#include "mainwindow.h"
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QPushButton* button = new QPushButton("Quit");
    QObject::connect(button, SIGNAL( clicked() ),
            &app, SLOT(quit()) );
    button->show();
    return app.exec();
}

Qt5の学習

Qt学習方法について

現在は、Qt5での開発が主流になっています。

Qt5に関連する情報はまだ少ないです。 ドキュメントは現在ほどんど英語しかないため、英語のドキュメントを読むことは回避しにくいです。 以下、ドキュメント。

http://doc.qt.io/qt-5/

Qt Creatorのサンプルが素晴らしいので、それを活用するといいと思われます。 (私の環境では、Qt Creator 4.3.1です。)

おすすめのサンプルは、Designing the User interfaceがおすすめです。

このサンプルは、Qt Designerを使用したものではないので、Qt Designerの学習は別途必要です。

f:id:nprogram:20170726094001p:plain

コードレビューで指摘していただいた箇所まとめ [C++]

コードレビューで指摘していただいた箇所をまとめて記載したいと思います。

  • ポリモーフィズムのための基底クラスには、仮想デストラクタを宣言すること。なぜならデストラクタが virtual でない場合、親クラスの型のポインタを delete した際には親クラスのデストラクタしか呼ばれないため。
// ~Parent is not virtual.
Parent* parent = new Child();
...
delete parent;  // this always calls ~Parent(); ~Child() is never called.
  • デフォルトコンストラクタの引数はvoidと明示して記載すべき
class A {
public:
    A (void) {}

}
  • 単体テストを書きましょう nprogram.hatenablog.com

  • ヘッダ内では、極力他のヘッダをインクルードしないようにしましょう

    • 2重インクルードや、循環参照(*1)を引き起こします。循環参照問題とは、ヘッダーファイルAがヘッダーファイルBをインクルードし、ヘッダーファイルBがヘッダーファイルAをインクルードする事。
    • クラスの前方宣言を有効活用しましょう。ヘッダの余計なインクルードを減らすことができます。
普通の前方宣言(ClassAのメンバ変数としてClassBを持つ場合)
class classB;
class classA
{
public:
    classA();
    ~classA();
private:
    classB *objB;
};
  • クラス名、メソッド名、変数名をしっかり考えよう
    • 例えば、犬クラスは、犬という情報を表現するクラスになっていますか?犬クラスなのに、犬クラスの中にスマートフォンを操作するメソッドがあってはいけません。( ´∀` )
  • コーディングスタイルが統一しましょう
    • スペース
    • 中かっこの位置
  • for文(繰り返し文)の中に、繰り返すたびに呼び出す必要がないのに、呼び出している処理はありませんか。
    • for文の外に処理を映しましょう
  • constが使えるのに、constを使っていない変数、メソッド引数はありませんか。
    • constが使える場合には、constを使いましょう
    • コンストラクタ初期化子で、メンバ変数を初期化した後、メンバ変数を初期化しない場合は、メンバ変数をconst修飾可能です。
  • ポインタ渡しと参照渡し、どちらでも実現できる場合は、参照渡しにしたほうが良いと思います
    • 参照渡しは、実体がなければ実施できないため、呼び出し先のメソッドで、NULLポインタチェックが必要にならない
  • staticクラスなど、インスタンスを生成しないクラスは、コンストラクタに対して、delete宣言をして、コンストラクタの暗黙定義を明示的に禁止すべきである

  • 例外を投げない場合はnoexceptで関数を宣言すること。noexceptを指定することは大きなメリットがあるが、もしnoexceptを指定した関数に修正を加えてnoexceptを指定しないように変更した場合はクライアントコードを壊す可能性がある。 noexceptは可能なら常に指定すべきだが、今後もnoexceptであり続けると考えられる関数に限るべき。

  • main関数のreturn文は、EXIT_SUCCESSを使うことが望ましい return EXIT_SUCCESS;

  • class宣言をfinal修飾子をつけると、そのクラスは継承不可となる
class BaseClass final   
{  
};  
  
class DerivedClass: public BaseClass // compiler error: BaseClass is   
                                     // marked as non-inheritable  
{  
};  
  • 例外を送出する可能性があるかないかのみを指定する。例外を送出する可能性がある関数にはnoexcept(false)を指定し、例外を送出する可能性がない関数にはnoexcept(true)もしくはnoexceptを指定する
  // getValue()メンバ関数は、例外を送出しない
  int getValue() const noexcept
  {
    return value_;
  }
  • constexprは、「constant expression (定数式)」の略語である。この機能を使用することで、コンパイル時に値が決定する定数、コンパイル時に実行される関数、コンパイル時にリテラルとして振る舞うクラスを定義できる。static constexpr int NUM=10;

  • 条件演算子を含む式では、式ごとにカッコで括ること。⇒ 静的解析ツールで警告が出るため

[OK]
if ( (0 < a) && (a < 10) ) {


[NG]
if ( 0 < a && a < 10) {
  • 全てのクラスオブジェクトで使う定数の宣言するとき, それがリテラル型(int, double, charなど)であればconstのかわりにconstexprを使うこと。クラス定義の中で値を初期化可能。
class nyan {
  constexpr static int i = 1;
  constexpr static double d = 1.2;
  constexpr static char str[] = "nyan!";
};
  • メンバ関数の右側にconstをつけると、そのメンバ関数内ではメンバ変数の変更ができなくなります。このメンバ関数をconstメンバ関数と呼ぶ。
class A
{
public:
    int m_Value;
    void Hoge( void ) const // ←このconstです
    {
    }
};
  • explicitを使用して、明示的に値渡しでコンストラクタを呼ばれないようにする
class A { public: A(int); };
class B { public: B(int); };

void f(const A&);
void f(const B&);

int main() {
f(3); // エラー:どっちを呼ぶか決定できない
return 0;
}

C++ クラス設計に関するノート www.ogis-ri.co.jp

コードレビューの観点まとめ [C++]

今回は、コードレビューの観点まとめについて、記載していきたいと思います。

<コードレビューでプラス事項>

  • 誰がひきついでもわかりやすく、読みやすいコード
  • 変数名や関数名が工夫されていてコメントが少なく読みやすいコード
  • 変数のスコープが必要最低限になっているコード
  • constとconstexprを適切に使って定数と変数が区別されているコード、インスタンス変数を書き換えないメソッド
  • 問題を解くためのデータ構造とアルゴリズムが隠蔽されていて、その2つを変更してもユーザーコードの変更がいらないように設計されているクラス
  • 使用変更や機能拡張が容易なコード
  • assertやStatic_assertが適切に使われており、事前条件を確認し、契約によるプログらイングができているコード
  • 再利用性が高くなるように責務分割されたコード

<コードレビューでマイナス事項>

  • 1つの関数であれもこれもやっている関数。凝縮度が低い関数。
  • ファイル分割されていないプログラム
  • ファイル分割のねらい、基準がはっきりしないプログラム
  • ユーザーにとってインタフェースが使いやすく設計されていないクラス。 (ユーザーコードがきれいに書けないインタフェース)
  • 階層化されておらず、レベルの違う処理を順番に読み出すだけの関数

独自クラスをstd::vectorに格納する [C++]

はじめに

std::vector に独自クラスを入れる場合、コピーコンストラクタとムーブコンストラクタのいずれかが必要になります。

コピーコンストラクタについては、このページに詳しく書かれています。 http://nobunaga.hatenablog.jp/entry/2016/07/03/230337

コード例

以下にサンプルコードを記載します。

#include <iostream>
#include <vector>

class Ball {
    
private:
    int m_radius;
    
public:

    // コンストラクタ
    Ball(int someRadius) : m_radius(someRadius)
    {

    }
    
    int getRadius(){return m_radius;}
    int getDiameter(){return m_radius * 2;}
    
    // コピーコンストラクタ
    Ball (const Ball& rhs) : m_radius(rhs.m_radius)
    {
    }
};

int main(void){
    
    std::vector<Ball> v;
    
    v.push_back(Ball(3));
    v.push_back(Ball(5));
    v.push_back(Ball(6));
    
    for(auto item : v)
    {
        std::cout << "Radius : " << item.getRadius() << ", Diameter : " << item.getDiameter() << std::endl;
    }
}
Radius : 3, Diameter : 6
Radius : 5, Diameter : 10
Radius : 6, Diameter : 12

vector::push_backのテスト

次に、独自クラスをvector::push_backを使ってstd::vectorに格納した場合、コンストラクタとデストラクタ、コピーコンストラクタがどの程度呼び出されているか調べてみました。

#include <iostream>
#include <vector>

class Ball {
    
private:
    int m_radius;
    
public:
    
    // デフォルトコンストラクタ
    Ball();
    
    // コンストラクタ
    Ball(int someRadius) : m_radius(someRadius)
    {
        std::cout << "Call Constructor (Radius : " << someRadius << ")" << std::endl;
    }
    
    // デストラクタ
    ~Ball()
    {
        //std::cout << "Call Destructor (Radius : " << m_radius << ")" << std::endl;
    }
    
    
    int getRadius(){return m_radius;}
    int getDiameter(){return m_radius * 2;}
    
    // コピーコンストラクタ
    Ball (const Ball& rhs) : m_radius(rhs.m_radius)
    {
        std::cout << "Call Copy Constructor (Radius : " << m_radius << ")" << std::endl;
    }
};

int main(void){
    
    std::vector<Ball> v;
    
    v.push_back(Ball(1));
    v.push_back(Ball(2));
    v.push_back(Ball(3));
    v.push_back(Ball(4));
    v.push_back(Ball(5));
}
Call Constructor (Radius : 1)
Call Copy Constructor (Radius : 1)
Call Constructor (Radius : 2)
Call Copy Constructor (Radius : 2)
Call Copy Constructor (Radius : 1)
Call Constructor (Radius : 3)
Call Copy Constructor (Radius : 3)
Call Copy Constructor (Radius : 1)
Call Copy Constructor (Radius : 2)
Call Constructor (Radius : 4)
Call Copy Constructor (Radius : 4)
Call Constructor (Radius : 5)
Call Copy Constructor (Radius : 5)
Call Copy Constructor (Radius : 1)
Call Copy Constructor (Radius : 2)
Call Copy Constructor (Radius : 3)
Call Copy Constructor (Radius : 4)

vector::push_backに、ムーブコンストラクタを使用してみる

上記のコードで、Ballクラスにムーブコンストラクタを使用しました。

ムーブコンストラクタには、noexcept をつけましょう。(参考リンク参照)

noexcept修飾子をつけないと、std::vector の push_back 時にコピーコンストラクタが呼ばれてしまいます。(本コードでも確認済み)

理由

std::vector は push_back の内部で std::move_if_noexcept() を使って要素を移動している。

この std::move_if_noexcept() がどのような動作をするかというと、

ムーブコンストラクタに noexcept 修飾子を付がついている(=「このコンストラクタは例外を投げないよ」とコンパイラに明示している)ときには右辺値参照&&をリターン noexcept 修飾子がついていないときは const 左辺値参照をリターン

#include <iostream>
#include <vector>

class Ball {
    
private:
    int m_radius;
    
public:
    
    // デフォルトコンストラクタ
    Ball();
    
    // コンストラクタ
    Ball(int someRadius) : m_radius(someRadius)
    {
        std::cout << "Call Constructor (Radius : " << someRadius << ")" << std::endl;
    }
    
    // デストラクタ
    ~Ball()
    {
        //std::cout << "Call Destructor (Radius : " << m_radius << ")" << std::endl;
    }
    
    
    int getRadius(){return m_radius;}
    int getDiameter(){return m_radius * 2;}
    
    // コピーコンストラクタ
    Ball (const Ball& rhs) : m_radius(rhs.m_radius)
    {
        std::cout << "Call Copy Constructor (Radius : " << m_radius << ")" << std::endl;
    }
    
    // ムーブコンストラクタ
    Ball(Ball&& rhs) noexcept : m_radius(rhs.m_radius)  
    {
        std::cout << "Call Move Constructor (Radius : " << m_radius << ")" << std::endl;
        
    }
};

int main(void){
    
    std::vector<Ball> v;
    
    v.push_back(Ball(1));
    v.push_back(Ball(2));
    v.push_back(Ball(3));
    v.push_back(Ball(4));
    v.push_back(Ball(5));
}
    
Call Constructor (Radius : 1)
Call Move Constructor (Radius : 1)
Call Constructor (Radius : 2)
Call Move Constructor (Radius : 2)
Call Move Constructor (Radius : 1)
Call Constructor (Radius : 3)
Call Move Constructor (Radius : 3)
Call Move Constructor (Radius : 1)
Call Move Constructor (Radius : 2)
Call Constructor (Radius : 4)
Call Move Constructor (Radius : 4)
Call Constructor (Radius : 5)
Call Move Constructor (Radius : 5)
Call Move Constructor (Radius : 1)
Call Move Constructor (Radius : 2)
Call Move Constructor (Radius : 3)
Call Move Constructor (Radius : 4)

参考リンク