はじめに
この記事は前回の記事の続きになります。
前回の内容はCMakeLists.txtを使ったビルドでした。
今回はソースコードをきれいにまとめる術を書いていきたいと思います。
突然ですが、自作したヘッダーファイルをソースコードに読み込ませる時に#include
を使うと思います。
#include
を読み込む時にソースコードとヘッダーファイルの階層が違うと、ディレクトリ名を含めなければいけません。
(含めないと、どのファイルを読み込むのか、ソースコードはわかりません)
しかし、ディレクトリ名をソースコードに書くのは、少しかっちょ悪い。
なので、CMakeLists.txtに書いてしまいましょう。
というのが今回の記事になります。
参考サイト
-
かみのメモ
- 1番読んだサイト
- CMakeの使い方
- 実践C++ 応用講座CMake編
目次
まずはビルド
ファイルの準備
今回のディレクトリ構成は以下になります。
.
├── CMakeLists.txt
├── sampleApp.cpp
└── util/
└── file.h
sampleApp.cppの他にfile.hを用意しました。
file.hはutilフォルダに置いておきます。
今回のCMakeLists.txtの内容はこちらです。
cmake_minimum_required(VERSION 3.1)
project(sampleApp CXX)
add_executable(sampleApp sampleApp.cpp)
target_include_directories(sampleApp PRIVATE ./util)
前回と比べて、target_include_directories()が加わりました。
sampleApp.cppとfile.hは以下になります。
#include <iostream>
#include "file.h"
int main(void)
{
Write writer;
Read reader;
std::string file_path = "./sample_text.txt";
writer.AddFile("Hello,World");
writer.AddFile("Hello,World!");
writer.AddFile("Hello,World!!");
writer.WriteFile(file_path);
reader.ReadFile(file_path);
std::cout << reader.GetLine(0) << std::endl;
std::cout << reader.GetLine(2) << std::endl;
std::cout << reader.GetLine(1) << std::endl;
}
#pragma once
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
class Read {
public:
int ReadFile(std::string file_path) {
std::ifstream ifs(file_path);
std::string str;
if (ifs.fail()) {
std::cerr << "Failed to open file." << std::endl;
return -1;
}
while (getline(ifs, str)) {
file_data.push_back(str);
}
return 0;
}
int LineSize() { return file_data.size(); }
std::string GetLine(int line) { return file_data[line]; }
private:
std::vector<std::string> file_data;
};
class Write {
public:
void WriteFile(std::string file_path) {
std::ofstream ofs(file_path);
for (int i = 0; i < file_data.size(); i++) {
ofs << file_data[i] << std::endl;
}
}
void AddFile(std::string data) { return file_data.push_back(data); }
private:
std::vector<std::string> file_data;
};
ビルド
前回と同様です。
mkdir build
cd build
cmake ..
cmake --build .
これにて、buildフォルダにsampleAppが作られます。
ファイル実行
$ ./sampleApp
Hello,World
Hello,World!!
Hello,World!
$ ls
CMakeCache.txt CMakeFiles Makefile cmake_install.cmake sampleApp sample_text.txt
coutの内容が出力され、sample_text.txtが生成されます。
記述内容の説明
CMakeLists.txtに記述した内容について説明していきます。
cmake_minimum_required(VERSION 3.1)
project(sampleApp CXX)
add_executable(sampleApp sampleApp.cpp)
target_include_directories(sampleApp PRIVATE ./util)
- 今回、追加したのは
target_include_directories(sampleApp PRIVATE ./util)
です。- target_include_directories
- 実行ファイル名、PRIVATE、リンクするフォルダを指定。
- PRIVATE以外にPUBLIC、INTERFACEが指定できますが、今はPRIVATEだけで大丈夫です。
- target_include_directories
- target_include_directoriesを追加したことによる効果は以下になります。
- 本来、
#include
は以下のようになる。sampleApp.cpp#include <iostream> #include "util/file.h"
- それをフォルダ名を無くすことができます。
sampleApp.cpp
#include <iostream> #include "file.h"
- 本来、
余談
ここからは余談です。
流してしまっても構いません。
『#include "util/file.h"
を#include "file.h"
にしなければいけない理由とは何か』
かっちょ悪いから・・・、だけなわけがありません。
理由はメンテナンスの容易さです。
ヘッダーファイルが増えていき、新しくフォルダを追加するとします。
そうすると、ソースコードのフォルダパスも修正しなければいけません。
ソースコードが1ファイルのみなら修正は容易です。
しかし、数ファイルあった場合は少し手間ですよね。
その手間を省くために、この技術が役に立ちます。
『今はPRIVATEだけで大丈夫です。->他が気になる。』
簡単に説明します。
target_include_directoriesでは、実行ファイルの後にPUBLIC
、PRIVATE
、INTERFACE
の3つが選べます。
それぞれの説明は以下になります。
- PUBLIC: コマンドの内容が"自分自身"と"自分に依存するターゲット"に反映される
- PRIVATE: コマンドの内容が"自分自身"にのみ反映される
- INTERFACE: コマンドの内容が"自分に依存するターゲット"にのみ反映される
今回、file.hは自分自身のみ使うので、PRIVATE
を指定しました。
他の実行ファイルから呼ばれることになれば、PUBLIC
を指定したかもしれません。
『file.hの実装長くね?』
すみません。
自分が将来使いそうな実装を残しちゃいました。
C言語出身だったので、fstreamは偉大だと思い、書きました。笑
さいごに
以上で終了です。
ご参考になれば幸いです。
関連記事
- 【初心者向け】CMakeLists.txtを使ってビルドする。
- 【初心者向け】CMakeLists.txtを使ってincludeのpathを省略する。
- 【初心者向け】CMakeLists.txtを使ってlibraryをリンクする。