はじめに
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)
参考リンク
独自クラスをvector::push_backを使ってstd::vectorに格納する
独自クラスをvector::emplace_backを使ってstd::vectorに格納する