この記事について
C++でクラスを定義する場合の必要最低限のテンプレートを記載します。C++11以上をターゲットにしています。クラスを定義する際のファイル構成と、ファイルの書き方、クラス定義の方法について記載します。
ファイル構成
プロジェクトProjectにおいて、パッケージPackageのライブラリlibを作成しており、ある基底クラスBaseClass、派生クラスDerivedClassを定義する場合を想定して説明します。構成方法はいろいろありますが、一つの方法として、以下のようなルールを採用して定義していきます。
- モジュール毎に/project/package/libのようにフォルダを階層化する
- 1ファイルに1つの公開クラスを定義する
- ファイル名は公開クラス名とする
- Class.hにクラス定義、Class.cppにクラス実体の実装を記載する
この場合、例えばファイル構成は以下のように行います。
project/ # ビルドのトップフォルダ
└ src/
└ package/
└ lib
├ BaseClass.cpp # BaseClassの実装
├ BaseClass.h # BaseClassの定義
├ DerivedClass.cpp # DerivedClassの実装
└ DerivedClass.h # DerivedClassの定義
クラス定義
基底クラス
基底クラスとなるBaseClassのヘッダ定義を説明します。クラス定義は原則ヘッダファイルに記載します。ヘッダファイルとなるBaseClass.hには最低限、以下の定義を記載します。
- ファイル説明
- インクルードガード
- 公開するインクルード定義
- 公開するマクロ定義
- 名前空間定義
- 公開クラス定義
- public関数
- private関数
- クラス定数
以下にテンプレートを記載します。コメントはdoxygen形式で記載します。
/**
* @file BaseClass.h
* @brief クラスの説明
* @note 特記事項など
* @author 作者名
* @copyright 著作権、ライセンスの説明を記載
*/
#ifndef ORGANIZATION_PROJECT_PACKAGE_LIB_CLASSNAME_H___
#define ORGANIZATION_PROJECT_PACKAGE_LIB_CLASSNAME_H___
// インクルードガードです
// 例えば以下のような構成でユニークな名称をつけることができます
// 実際のファイルパスも基準位置からこの構成と同じにすると整理しやすくなります
//
// (組織名)_(プロジェクト名)_(パッケージ名)_(ライブラリ名)_(クラス名)_H___
//-------------------------------------------------------
// includes
//-------------------------------------------------------
// 依存関係のあるヘッダのインクルードはここに記述します
#include <iostream>
#include <mutex>
//-------------------------------------------------------
// defines
//-------------------------------------------------------
// マクロ定義はここに記述します
//-------------------------------------------------------
// namespace
//-------------------------------------------------------
// 名前空間の定義はここに記述します
namespace organization
{
namespace project
{
namespace package
{
namespace lib
{
//-------------------------------------------------------
// public classes
//-------------------------------------------------------
// 公開クラスの定義はここに記述します
/**
* 基底クラスの最低限の定義
*/
class BaseClass
{
public:
/**
* デフォルトコンストラクタ
*/
BaseClass() {}
/**
* 引数ありコンストラクタ
* @param value
*/
explicit BaseClass(const std::string &value)
: privateMember_(value) {}
/**
* コピーコンストラクタ
* コピーを抑止したい場合は = deleteと指定します
*
* @param from コピー元インスタンス
*/
BaseClass(const BaseClass &from)
: privateMember_(from.privateMember_) {}
/**
* コピー代入演算子のオーバーロード
* 代入を抑止したい場合は = deleteと指定します
*
* @param from 代入元インスタンス
*/
BaseClass &operator=(const BaseClass &from);
/**
* ムーブコンストラクタ
* 必要に応じて宣言します
*/
BaseClass(BaseClass &&rhs) noexcept = delete;
/**
* ムーブ代入演算子
* 必要に応じて宣言します
*/
BaseClass &operator=(BaseClass &&rhs) noexcept = delete;
/**
* デストラクタ
* 基底クラスは必ずvirtualを付けます
*/
virtual ~BaseClass() {}
/**
* 公開関数
* 形式を極力統一するため、戻り値ではエラー情報を返し、
* 演算結果は引数に渡された出力用変数に代入するように定義します
*
* @param input 入力パラメータ
* @param output 出力を返す領域
* @retval エラー状態
*/
Error publicMethod(const std::string &input, std::string &output) noexcept;
/**
* getter系関数のように、実行しても
* メンバ変数の状態を変更しない関数はconstを付けます
* このような関数はスレッドセーフになるように設計します
*/
virtual std::string toString() const;
/**
* 純粋仮想関数
* Abstract Classとする場合にインターフェースとなる関数を定義します
*/
// virtual void abstractMethod() = 0;
//------------------------------------------
// クラス定数
//------------------------------------------
static constexpr uint32_t const CONST_INTEGER = 0; ///< コンパイル時定数とすることで配列の添え字に利用できます
static constexpr char *const CONST_STRING = (char *const) "compile-time constants"; ///< 文字列リテラルも定義可能です
private:
/**
* private変数
*/
std::string privateMember_;
// const関数をスレッドセーフにするためのMutexオブジェクトを使う場合は
// const関数内で変更できるようにMutexオブジェクトに「mutable」を指定します
// mutable std::mutex mutexToString_;
};
} // namespace lib
} // namespace package
} // namespace project
} // namespace organaization
#endif // include guard
クラス関数の実体実装を記載するcppファイルには以下の内容を記載します。
- ファイル説明
- 実装で使うインクルード定義
- 実装でのみ使うマクロ定義
- 名前空間
- 実装でのみ使う内部関数定義
- クラスの関数の実体定義
実装例は以下の通りです。
/**
* @file BaseClass.cpp
* @brief クラスの説明
* @note 特記事項など
* @author 作者名
* @copyright 著作権、ライセンスの説明を記載
*/
//-------------------------------------------------------
// includes
//-------------------------------------------------------
// 依存関係のあるヘッダのインクルードはここに記述します
#include "BaseClass.h"
//-------------------------------------------------------
// defines
//-------------------------------------------------------
// マクロ定義はここに記述します
//-------------------------------------------------------
// namespace
//-------------------------------------------------------
// 名前空間の定義はここに記述します
namespace organization
{
namespace project
{
namespace package
{
namespace lib
{
//-------------------------------------------------------
// private functions
//-------------------------------------------------------
// このファイル内で利用する関数がある場合はここに記載します
//-------------------------------------------------------
// public classes
//-------------------------------------------------------
// 公開クラスの定義はここに記述します
BaseClass &BaseClass::operator=(const BaseClass &from)
{
// 自己代入の防止
if (this != &from)
{
this->privateMember_ = from.privateMember_;
}
return *this;
}
Error BaseClass::publicMethod(const std::string &input, std::string &output) noexcept
{
static std::string add_str = "-added";
// 関数の計算結果はアウトプットを表す引数に戻します
output = input + add_str;
// 戻り値はエラー状態
return Error::Success();
}
std::string BaseClass::toString() const
{
std::string ret;
// スレッドセーフを保証するためにロックを取得
// std::lock_guard<std::mutex> lock(mutexToString_);
ret = privateMember_;
return ret;
}
} // namespace lib
} // namespace package
} // namespace project
} // namespace organaization
派生クラス
基本的には基底クラスの場合と同様です。派生クラスの場合、継承するため、基底クラスのコンストラクタの呼び出しやオーバーライドの宣言などが追加されます。以下に定義例を記載します。
/**
* 派生クラスの定義
*/
class DerivedClass : public BaseClass
{
public:
/**
* 派生クラスのコンストラクタ
* 基底クラスのコンストラクタを呼び出します
*/
DerivedClass()
: BaseClass() {}
explicit DerivedClass(const std::string &value)
: BaseClass(value) {}
/**
* デストラクタ
* 派生クラスの場合はオーバーライドを宣言します
*/
~DerivedClass() override {}
/**
* オーバーライドを行う関数にも明示的にoverrideを宣言します
*/
std::string toString() const override;
};
override指定子はクラス定義内のみで宣言します。
std::string DerivedClass::toString() const /*overrideはここでは宣言しない*/
{
static std::string msg = "Derived Class Message";
return msg;
}
補足
エラー情報クラス
上記の例で使用した関数の戻り値で使うエラー情報クラスは例えば以下のように実装できます。
/**
* @file ErrorStatus.h
* @brief 関数の戻り値となるエラーステータスを表すクラス
* @note
* @author Name
* @copyright XXXX All Rights Reserved. YYYY license.
*/
#ifndef ORGANIZATION_PROJECT_PACKAGE_LIB_ERRORSTATUS_H___
#define ORGANIZATION_PROJECT_PACKAGE_LIB_ERRORSTATUS_H___
//-------------------------------------------------------
// includes
//-------------------------------------------------------
#include <iostream>
#include <memory>
#include <string>
//-------------------------------------------------------
// defines
//-------------------------------------------------------
//-------------------------------------------------------
// namespace
//-------------------------------------------------------
namespace organization
{
namespace project
{
namespace package
{
namespace lib
{
//-------------------------------------------------------
// public classes
//-------------------------------------------------------
/**
* エラー情報を定義します
* 必要なデバッグ情報がある場合、この定義内に適宜情報を追加定義します
*/
class ErrorInfo
{
public:
ErrorInfo() {}
explicit ErrorInfo(const std::string &str) : errstring_(str) {}
std::string toString() const
{
return errstring_;
}
private:
std::string errstring_; ///< エラーメッセージ
// uint32_t errcode; などを必要あれば追加
};
/**
* エラーステータスを表すクラスです
*/
template <class Info>
class ErrorStatus
{
public:
/**
* デフォルトコンストラクタ (Successのケースで利用)
*/
ErrorStatus() {}
/**
* 引数ありコンストラクタ (Failureのケースで利用)
* @param in エラー情報
*/
explicit ErrorStatus(const Info &in)
{
// エラー情報をコピーして格納します
p_info_ = std::unique_ptr<Info>(new Info);
*p_info_ = in;
}
/**
* コピーコンストラクタ
* @param from コピー元インスタンス
*/
ErrorStatus(const ErrorStatus &from)
: p_info_((from.p_info_ == nullptr) ? nullptr : new Info(*from.p_info_)) {}
/**
* 代入演算子のオーバーロード
* @param from 代入元インスタンス
*/
ErrorStatus &operator=(const ErrorStatus &from)
{
// 自己代入の防止
if (this != &from)
{
if (p_info_ != from.p_info_)
{
if (from.p_info_ != nullptr)
{
p_info_ = std::unique_ptr<Info>(new Info(*(from.p_info_)));
}
else
{
p_info_ = nullptr;
}
}
}
return *this;
}
/**
* 等価比較演算子のオーバーロード
* @param other 比較インスタンス
*/
bool operator==(const ErrorStatus<Info> &other) const
{
bool ret = false;
// 同じインスタンス
if( this->p_info_ == other.p_info_ )
{
ret = true;
}
else
{
// 内容が同じ情報であれば論理的に同じとみなす
if( !(this->isSuccess()) && !(other.isSuccess()) )
{
ret = ( this->p_info_->toString() == other.p_info_->toString() );
}
}
return ret;
}
/**
* 非等価比較演算子のオーバーロード
* @param other 比較インスタンス
*/
bool operator!=(const ErrorStatus<Info> &other) const
{
return !(*this == other);
}
/**
* 成功かどうかを返します
* @retval true 成功
* @retval false 失敗
*/
bool isSuccess() const
{
return (p_info_ == nullptr);
}
/**
* エラーメッセージを返します
* @retval エラーメッセージ
*/
std::string message()
{
// Successの場合の空の文字列オブジェクトをここで作成
static std::string no_msg = std::string();
return isSuccess() ? no_msg : p_info_->toString();
}
/**
* 成功を表すオブジェクトを返します
*/
static ErrorStatus<Info> Success()
{
return ErrorStatus();
}
/**
* 失敗を表すオブジェクトを返します
*/
static ErrorStatus<Info> Failure(const Info &in)
{
return ErrorStatus(in);
}
private:
/**
* エラー情報を保持する変数
* Successの場合はnullptr、Failureの場合は非NULLで情報を持つ
*/
std::unique_ptr<Info> p_info_;
};
/**
* 利用側で使いやすくするためのリネームです
*/
using Error = ErrorStatus<ErrorInfo>;
} // namespace lib
} // namespace package
} // namespace project
} // namespace organaization
#endif // include guard