プログラミング言語「C++」についてどんな言語なのか解説

C++はC言語を拡張したプログラミング言語で、様々な開発に用いられています。

C言語と比べると習得難易度が高い言語ですが、そのぶん高度なプログラムを作成することができます。

この記事では、C++とはどういうプログラミング言語なのか解説します。

目次

C++とは

C++は、オブジェクト指向プログラミング言語の一つであり、C言語を拡張したものです。

C++は、C言語と同様に高速で効率的なコードを生成することができますが、オブジェクト指向プログラミングの特徴を持っています。

オブジェクト指向プログラミングは、データとそのデータに対する操作を1つの単位(オブジェクト)として扱うことができるため、大規模なプログラムでも保守性や再利用性が高くなります。

また、C++は多重継承やテンプレートなどの機能も備えており、柔軟かつ強力なプログラミングが可能であり、ゲーム開発や組み込みシステム開発などにも利用されています。

C++の特徴

C++は、オブジェクト指向プログラミング言語の一つであり、C言語を拡張したものです。C++には以下のような特徴があります。

オブジェクト指向プログラミング

C++はオブジェクト指向プログラミング言語であるため、データとそれに関連する操作をまとめて扱うことができます。これにより、プログラムの保守性や再利用性が高まります。

ポインタ

C++ではポインタをC言語より安全かつ効率的に使用することができます

ポインタを使うことで、メモリ上のアドレスを直接操作することが可能になります。また、動的メモリ確保もC++で新たに追加されたnew/deleteを使用することで、柔軟なプログラム作成が可能です。

演算子の多様性

C++では演算子の多様性があります。例えば、+演算子は数値同士だけでなく文字列や配列でも使用することができます。

テンプレート

C++にはテンプレート機能があります。テンプレートを使用することで汎用的なコードを書くことが可能になります。

C++の基本構文

C++の基本構文について解説します。

変数

変数は、データを格納するためのメモリ領域を確保するために使用されます。

C++では、変数を宣言する際に、データ型と変数名を指定します。例えば、整数型の変数xを宣言する場合は以下のようになります。

int x;

また、初期値を設定することもできます。

int x = 10;

演算子

C++には、四則演算や比較演算などの演算子があります。

分類演算子演算の種類
算術演算子+加算
減算
*乗算
/除算
比較演算子==等しい
!=等しくない
<より大きい
>より小さい
<=以上
>=以下
論理演算子&&かつ
||または
!否定
ビット演算子&AND
|OR
^XOR
~NOT

制御構文

制御構文は、プログラムの流れを制御するために使用されます。代表的な制御構文を以下に示します。

if文

if文は条件分岐処理で使用される制御構文です。条件式が真である場合にブロック内の処理が実行されます。

if (条件式) {
    // 条件式が真である場合の処理
}

for文

for文は繰り返し処理で使用される制御構文です。指定した回数だけブロック内の処理が繰り返し実行されます。

for (初期化式; 条件式; 更新式) {
    // 繰り返し処理
}

while文

while文も繰り返し処理で使用される制御構文です。条件式が真である限りブロック内の処理が繰り返し実行されます。

while (条件式) {
    // 繰り返し処理
}

関数

関数は、一連の処理をまとめて呼び出すことができるプログラム単位です。

関数は引数を受け取って何らかの結果を戻すことも可能です。関数定義時に引数や戻り値の型を指定します。

戻り値の型 関数名(引数1, 引数2, ...) {
    // 処理内容
    return 戻り値;
}

例えば、Hello, World!と表示する関数hello_world()を作成する場合は以下のようになります。

void hello_world() {
    std::cout << "Hello, World!" << std::endl;
}

このようにして作成した関数は、以下のように呼び出すことができます。

hello_world();  // Hello, World! と表示される。

以上がC++の基本構文です。

クラスとオブジェクト

C++はオブジェクト指向プログラミング言語であり、クラスとオブジェクトの概念が重要です。

クラスは、データメンバーとメソッドをまとめたもので、オブジェクトはそのクラスのインスタンスです。

クラスの定義

クラスを定義するには、以下のような構文を使用します。

class クラス名 {
  // データメンバー
  // メソッド
};

例えば、以下のようにPersonという名前のクラスを定義することができます。

#include <iostream>

class Person {
public:
  std::string name;
  int age;
  
  void introduce() {
    std::cout << "私の名前は" << name << "です。" << std::endl;
    std::cout << "年齢は" << age << "歳です。" << std::endl;
  }
};

この例では、Personという名前のクラスが定義されています。この中には、nameageという2つのデータメンバーがあります。また、introduce()というメソッドも定義されています。

オブジェクトの生成

オブジェクトを生成するには、以下のような構文を使用します。

クラス名 オブジェクト名;

例えば、先ほど定義したPersonクラスからオブジェクトを生成する場合は以下のようになります。

Person person1;
person1.name = "山田太郎";
person1.age = 20;
person1.introduce();

この例では、Person型の変数person1を宣言しました。そして、nameageなどのデータメンバーに値を代入しました。最後にintroduce()メソッドを呼び出しています。

クラスの継承

C++では継承(inheritance)もサポートしており、既存のクラスから新しいクラスを作成することができます。

継承元(親)となる既存のクラスを基底(base)クラスまたは親(parent)クラスと呼びます。

そして新しく作成される派生(derived)または子(child)クラスでは基底(親)から継承されたデータや関数等を利用することができます。

以下は継承元(親)であるAnimalClassから派生したDogClass CatClass BirdClass の3つの子孫(derived) クラスです。

class AnimalClass{
public:
    virtual void cry(){std::cout<<"動物鳴く"<<std::endl;}
};

class DogClass:public AnimalClass{
public:
    void cry(){std::cout<<"わんわん"<<std::endl;}
};

class CatClass:public AnimalClass{
public:
    void cry(){std::cout<<"ニャーニャー"<<std::endl;}
};

class BirdClass:public AnimalClass{
public:
    void cry(){std::cout<<"チュンチュン"<<std::endl;}
};

上記コードではAnimalClass を基底(親) DogClass CatClass BirdClass それぞれ派生(子) させています。 各々cry関数内で異なる音声出力処理が行われていることが確認できます。

ポインタと参照

ポインタとは

ポインタは、メモリ上のアドレスを格納するための変数です。C++では、ポインタを使って直接メモリにアクセスすることができます。

int num = 10;
int *pNum = # // ポインタ変数にnumのアドレスを代入

上記の例では、&演算子を使ってnum変数のアドレスを取得し、それをpNumというポインタ変数に代入しています。

ポインタの演算

ポインタは、加算や減算などの演算が可能です。これにより、配列や文字列などのデータ構造に対して効率的な処理が行えます。

int arr[3] = {1, 2, 3};
int *pArr = arr; // 配列arrの先頭要素へのポインタを代入
cout << *pArr << endl; // 出力結果:1
pArr++; // ポインタを次の要素に移動
cout << *pArr << endl; // 出力結果:2

上記の例では、配列arrの先頭要素へのポインタをpArrに代入しています。

そして、*pArrで先頭要素(つまり1)にアクセスし、その後ポインタを次の要素に移動させています。移動後は、再度*pArrで値(つまり2)にアクセスしています。

参照とは

参照は、既存の変数に別名を付けることができる機能です。

参照は通常、「&」演算子で定義されます。参照型変数自体が新しいメモリ領域を確保するわけではありません。そのため、参照型変数自体へ値を代入することはできません。

int num = 10;
int &refNum = num; // 参照型変数refNum を定義し、num を参照させる.  
cout << refNum << endl; // 出力結果:10
refNum++; // 参照先(num) の値が増加する.  
cout << num << endl; // 出力結果:11 

上記例では、整数型変数 num を定義し、その値 10 を初期化します。

そして、整数型参照型変数 refNum を定義し、この参照型変数 refNum が整数型変数 num を参照させます。

最後に、参照先num)の値も同時に増加させています。

テンプレート

テンプレートは、C++の強力な機能の一つであり、汎用的なコードを書くために使用されます。

テンプレートを使用することで、同じコードを複数回書く必要がなくなり、より効率的かつ柔軟性の高いコードを作成することができます。

テンプレート関数

テンプレート関数は、引数や戻り値の型が決まっていない関数です。以下は、2つの引数を受け取り、それらを加算して返すテンプレート関数Addです。

template <typename T>
T Add(T a, T b)
{
    return a + b;
}

この例では、template <typename T>という行がテンプレート宣言です。Tは任意の型名であり、この場合は2つの引数aとbおよび戻り値の型に使用されます。Add関数内では、abがT型であることが保証されています。

以下はAdd関数を呼び出す例です。

int result1 = Add<int>(3, 4); // result1 = 7
double result2 = Add<double>(1.5, 2.5); // result2 = 4.0

この例では、Add<int>およびAdd<double>という形式でテンプレート引数を指定しています。これにより、それぞれint型およびdouble型に対応したAdd関数が生成されます。

テンプレートクラス

テンプレートクラスは、クラス自体が汎用的なものになることを可能にします。以下は、任意のデータ型に対応したStackクラスの例です。

template <typename T>
class Stack {
public:
    void Push(T value);
    T Pop();
private:
    std::vector<T> m_data;
};

template <typename T>
void Stack<T>::Push(T value)
{
    m_data.push_back(value);
}

template <typename T>
T Stack<T>::Pop()
{
    if (m_data.empty()) {
        throw std::out_of_range("Stack is empty");
    }
    T value = m_data.back();
    m_data.pop_back();
    return value;
}

この例では、template <typename T> class Stackという行がテンプレート宣言です。

Stackクラス内では、T型オブジェクトを格納するstd::vector<T> m_dataメンバ変数およびPushメソッドおよびPopメソッドが定義されています。

以下はStackクラスを使用する例です。

Stack<int> stack;
stack.Push(1);
stack.Push(2);
int value1 = stack.Pop(); // value1 = 2
int value2 = stack.Pop(); // value2 = 1

この例では、「Stack<int>」という形式でテンプレート引数を指定しています。これにより、int型に対応したStackクラスが生成されます。

STL

STL(Standard Template Library)は、C++の標準ライブラリの一部であり、コンテナ、アルゴリズム、イテレータなどの機能を提供します。

コンテナ

コンテナは、データを格納するためのオブジェクトです。STLには、以下のようなコンテナがあります。

  • vector:可変長配列
  • deque:両端キュー
  • list:双方向連結リスト
  • set/multiset:重複要素を許さない/許す集合
  • map/multimap:キーと値をペアで管理する/重複キーを許すマップ

これらのコンテナは、それぞれ異なる特徴を持っています。

例えば、vectorはランダムアクセスが高速である一方で挿入・削除が遅く、listは挿入・削除が高速である一方でランダムアクセスが遅いという特徴があります。

アルゴリズム

STLには多数のアルゴリズムが用意されており、これらのアルゴリズムはコンテナに対して様々な操作を行うことが出来ます。例えば以下のようなものがあります。

  • sort:ソート
  • find_if:条件に合致する最初の要素を探索
  • copy_if:条件に合致する要素だけを別のコンテナにコピー

これらのアルゴリズムは非常に汎用的であり、自分で実装する必要性が低くなっています。

イテレータ

イテレータとは、「反復子」とも呼ばれるオブジェクトであり、STLでは主にコンテナ内部の要素に順番にアクセスするために使用されます。

イテレータはポインタと似たような使い方をしますが、ポインタよりも抽象度合いが高く汎用的です。またイテレータ自体もSTL内部ではコンテナと同じように扱われるため、「イテレータ」自体も型として扱われます。

以上がSTLについて簡単に説明した内容です。C++プログラミングではSTLを上手く活用することで効率的かつ簡潔なプログラミングを行うことが出来ます。

例外処理

C++には、プログラムの実行中にエラーが発生した場合に例外を投げることができます。

例外処理は、プログラムの安定性を高めるために非常に重要な機能です。

例えば、ファイルを開く際にエラーが発生した場合、プログラムはクラッシュしてしまいます。

しかし、例外処理を使用することで、このようなエラーが発生した場合でもプログラムを継続的に実行することができます。

例外処理はtry-catch文を使用して実装されます。tryブロック内で例外が発生した場合、catchブロックでその例外をキャッチし、適切な処理を行います。

以下は、ファイルの読み込み時に発生する可能性のあるエラーをキャッチする例です。

#include <iostream>
#include <fstream>

int main() {
    std::ifstream file("example.txt");
    try {
        if (!file) {
            throw "File not found";
        }
        // ファイルからデータを読み込む処理
    } catch (const char* error) {
        std::cerr << "Error: " << error << std::endl;
    }
    return 0;
}

上記のコードでは、ファイルが見つからなかった場合に"File not found"という文字列をthrowしています。

そしてcatchブロックでその文字列をキャッチし、エラーメッセージを表示しています。

また、C++では複数のcatchブロックを使用することもできます。以下は複数の型の例外をキャッチする方法です。

try {
    // 何らかの処理
} catch (const char* error) {
    // エラーメッセージ1
} catch (std::exception& e) {
    // エラーメッセージ2
} catch (...) {
    // その他のエラーメッセージ
}

最初のcatchブロックではconst char*型の例外(文字列)をキャッチし、次のcatchブロックではstd::exception型(標準的なC++例外)をキャッチします。最後のcatchブロックではどんな型でもキャッチすることが出来ます。

以上がC++における例外処理についてです。適切な使い方や実装方法はプログラマー自身が考えて決める必要があります。

マルチスレッド

マルチスレッドとは、複数のスレッドを同時に実行することで、プログラムの処理速度を向上させる技術です。C++では、標準ライブラリである<thread>を使用してマルチスレッドを実現することができます。

スレッドとは

スレッドとは、プログラム内で独立した処理の流れを表すものです。通常、プログラムは1つのスレッドで動作しますが、マルチスレッドでは複数のスレッドが同時に動作します。

スレッドの生成

C++では、std::threadクラスを使用して新しいスレッドを生成することができます。以下は、std::threadクラスを使用して新しいスレッドを生成する例です。

#include <iostream>
#include <thread>

void thread_func() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t(thread_func);
    t.join();
    return 0;
}

この例では、std::threadクラスのコンストラクタに関数ポインタ(または関数オブジェクト)を渡すことで新しいスレッドを生成しています。そして、join()メソッドによって親スレッドが子スレッドの終了まで待機するようになっています。

スレッド間通信

マルチスレッドでは複数のスレッドが同時に動作するため、異なるスレッド間でデータやリソースを共有する必要があります。

C++では、標準ライブラリである<mutex><condition_variable>などを使用して、複数のスレッド間で安全にデータやリソースを共有することができます。

以下は、2つの異なるスレッドから同じ変数にアクセスする例です。

#include <iostream>
#include <thread>
#include <mutex>

int counter = 0;
std::mutex mtx;

void increment_counter() {
    for (int i = 0; i < 100000; ++i) {
        mtx.lock();
        ++counter;
        mtx.unlock();
    }
}

int main() {
    std::thread t1(increment_counter);
    std::thread t2(increment_counter);

    t1.join();
    t2.join();

    std::cout << "Counter value: " << counter << std::endl;

    return 0;
}

この例では、2つの異なるスレッドから同じ変数(counter)にアクセスしています。

しかし、両方の処理が同時に実行されてしまうと正しくカウントアップされなくなってしまいます。

そこで、std::mutexクラスを使用してロック・アンロック処理を行うことで両方の処理が順番に実行されるように制御しています。

スレッドプール

マルチコアCPU環境下では多くの場合、「一つ一つ個別に新しいタイムシェアリング可能なOSプロセサー上へ移動させて」実行させるよりも、「事前確保された決まった量だけ存在するワーカースレット群へ分散させて」効率的かつ高速化させる方法もあります。

これら「決まった量だけ存在するワーカースレット群」全体自体も「ThreadPool」と呼ばれます。 C++11以降からは標準化されたThreadPoolも提供されており、簡単にマルチスレッド化することができます。

#include <iostream>
#include <vector>
#include <future>

int main()
{
   // ワーカーステートメント
   auto worker = [](const int id)
   {
      printf("Worker %d started\n", id);
      // 適当な時間待機
      std::this_thread::sleep_for(std::chrono::seconds(1));
      printf("Worker %d finished\n", id);
   };

   // ThreadPool の初期化
   const int num_workers = 4;
   std::vector<std::future<void>> futures(num_workers);
   for (int i = 0; i != num_workers; ++i)
      futures[i] = std::async(std::launch::async, worker, i);

   // 全 Worker の完了待ち合わせ
   for (auto& f : futures)
      f.wait();

   return EXIT_SUCCESS;
}
Worker 0 started
Worker 2 started
Worker 3 started
Worker 1 started
Worker 2 finished
Worker 0 finished
Worker 3 finished
Worker 1 finished

この例では、「4個分だけワーカー用意したThreadPool」内部へ「4個分だけ適当な仕事割り振って」それら全員完了後「次へ進む」という流れです。

以上がC++言語上から利用可能なマルチコアCPU環境下向けの簡単なThreadPool利用方法です。

C++の応用

C++は、その高速性や柔軟性から、様々な分野で利用されています。ここでは、その中でも特に注目される3つの応用分野について解説します。

ゲーム開発

C++は、ゲーム開発において広く使われている言語です。

その理由としては、高速な処理が求められるゲームにおいて、C++が優れたパフォーマンスを発揮することが挙げられます。

有名なゲームエンジン「Unreal Engine」などでも使われています。

また、オブジェクト指向プログラミングをサポートしており、大規模なゲーム開発にも適しています。

ネットワークプログラミング

C++は、ネットワークプログラミングにも利用されます。

例えば、Webサーバーやデータベースサーバーなどの高負荷なシステムを構築する場合には、処理速度が早いC++が採用されることが多いです。これは、高速かつ安定した処理が求められるためです。

機械学習

最近では、機械学習の分野でもC++が利用されるようになってきました。

特に深層学習のフレームワークであるTensorFlowやPyTorchでは、C++ APIが提供されており、高速かつ効率的な計算を行うことができます。

以上のように、C++は幅広い分野で活躍しています。これからもさらなる進化が期待されます。

目次