nprogram’s blog

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

独自クラスを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)

参考リンク