2023.04.19

C++開発とCMake ~その1 : CMake入門~

C++で開発したい。今までXcodeとかに環境は良い感じにしてもらっていたが、CMake入門する。 とりあえず今回はC++ファイルを作成しCMakeでコンパイルして実行するまで行う。

そもそも

C++で実行ファイルを作るにはコンパイルしないといけないが、クロスプラットフォームで開発する場合に環境ごとにコンパイラを選定することや、コンパイル手順を設定するのは大変やっかいである。それらが面倒なのでCMakeにもろもろお願いする。

vscode

VSCode環境でcmakeを使う。

拡張機能

ここを参考に設定する。デバッガはLLDBを使う。

F5またはShift + CMD + Pでコマンドパレットを開いてCMake : Debugを実行。

簡単なプロジェクト

C++ファイルを追加して使う簡単なプロジェクトを作る。 csvをパースして表示する簡単なクラスCSVParserと値を保存するだけのMyClassを作成

コード : github.com/gollowars/cpp_cmake_bootstrap

ファイルツリー

├── CMakeLists.txt
├── data
│   └── sample.csv
├── include
│   ├── CSVParser.h
│   └── MyClass.h
└── src
    ├── CSVParser.cpp
    ├── MyClass.cpp
    └── main.cpp
main.cpp
#include <iostream>
#include <filesystem>
#include "MyClass.h"
#include "CSVParser.h"

int main(int, char**) {
    std::cout << "Hello, world test7 !\n";

    if (__cplusplus == 201703L) std::cout << "C++17\n";
    else if (__cplusplus == 201402L) std::cout << "C++14\n";
    else if (__cplusplus == 201103L) std::cout << "C++11\n";
    else if (__cplusplus == 199711L) std::cout << "C++98\n";
    else std::cout << "Pre-standard C++\n";


    MyClass my_class(42);
    std::cout << my_class.get_value() << std::endl;
    

    std::filesystem::path current_path = std::filesystem::current_path();

    std::filesystem::path data_path = current_path / "data";

    std::filesystem::path sample_path = data_path / "sample.csv";


    CSVParser parser(sample_path.string());
    parser.parse();
    parser.print();


    return 0;
    
}
sample.csv
name,age,gender
Alice,25,Female
Bob,30,Male
Charlie,40,Male
MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass {
public:
    MyClass(int value);
    int get_value() const;

private:
    int m_value;
};

#endif
MyClass.cpp
#include "MyClass.h"

MyClass::MyClass(int value) : m_value(value) {}

int MyClass::get_value() const {
    return m_value;
}
CSVParser.h
#ifndef CSV_PARSER_H
#define CSV_PARSER_H

#include <string>
#include <vector>

class CSVParser {
public:
    CSVParser(const std::string& filename);
    ~CSVParser();
    void parse();
    void print();

private:
    std::string _filename;
    std::vector<std::string> _fields;
};

#endif
CSVParser.cpp
#include "CSVParser.h"
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <iostream>

CSVParser::CSVParser(const std::string& filename)  {
    _filename = filename;
}

CSVParser::~CSVParser() {
    // nothing to do
}

void CSVParser::parse() {
    // TODO
    std::ifstream file(_filename);
    std::string line;


    if(file.is_open()){
        std::cout << "file is open" << std::endl;
    }else {
        std::cout << "file is not open" << std::endl;
    }

    while(std::getline(file, line)) {
        std::stringstream ss(line);
        std::string field;
        std::string linestr;
        while(std::getline(ss, field, ',')) {
            linestr += field + ",";
            // _fields.push_back(field);
        } 
        // std::cout << linestr << std::endl;
        _fields.push_back(linestr);
    }

    file.close();
}

void CSVParser::print(){
    std::cout << "size : " + std::to_string(_fields.size()) << std::endl;
    // TODO
    for(auto& field : _fields) {
        std::cout << field << std::endl;
    }
    std::cout << std::endl;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.0.0)
# プロジェクト名
project(Example VERSION 0.1.0)

# include(CTest)
# enable_testing()


# C++標準の設定
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True) ## C++11であることを指定して、C++11以外のコンパイラを使うとエラーにする



# buildターゲット(この場合はPROJECT_NAMEであるExampleに)main.cppを追加
add_executable(${PROJECT_NAME} ./src/main.cpp) 

# ヘッダーファイルの読み込み
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)

## ソースファイルを追加する 
set(MY_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_sources(${PROJECT_NAME} PRIVATE 
    ${MY_SOURCE_DIR}/MyClass.cpp
    ${MY_SOURCE_DIR}/CSVParser.cpp
)


## assetをコピーする
configure_file(${CMAKE_SOURCE_DIR}/data/sample.csv ${CMAKE_CURRENT_BINARY_DIR}/data/sample.csv COPYONLY)


get_target_property(headers ${PROJECT_NAME} SOURCES)
message(STATUS "headers : ${headers}") # これはあってもなくても良い。メッセージを出力する確認

これでCMake : Debugすると

Hello, world test7 !
C++17
42
file is open
size : 4
name,age,gender,
Alice,25,Female,
Bob,30,Male,
Charlie,40,Male,

と無事出力


今回の完成ファイルはこちら https://github.com/gollowars/cpp_cmake_bootstrap/tree/main

C++開発シリーズ

References