「何々の時に〇〇コンストラクタが呼出しされるが覚えられないので特訓」の続きです。細々と続けています。
今回は、Range-based for loopの要素型について、auto
、auto&
、auto&&
のそれぞれを指定した場合の挙動の違いを確認してみました。
実験用クラスXの定義
前回同様、コピーコンストラクタ、ムーブコンストラクタがinvokeされたかどうかを確認するデバッグ文を仕込んだ実験用クラスX
を使いまわします。
# pragma GCC diagnostic ignored "-Wunused-parameter"
# pragma GCC diagnostic ignored "-Wunused-variable"
# pragma GCC diagnostic ignored "-Wuninitialized"
# include <iostream>
# include <memory>
class X {
public:
X() {
std::cout << this << " : constructor." << std::endl;
}
~X() {
std::cout << this << " : destructor." << std::endl;
}
X(const X& x) {
std::cout << this << " <-- " << std::addressof(x) << " : copy constructor." << std::endl;
}
X(X&& x) {
std::cout << this << " <-- " << std::addressof(x) << " : move constructor." << std::endl;
}
X& operator=(const X& x) {
std::cout << this << " <-- " << std::addressof(x) << " : copy assignment operator." << std::endl; return *this;
}
X& operator=(X&& x) {
std::cout << this << " <-- " << std::addressof(x) << " : move assignment operator." << std::endl; return *this;
}
void NonConstFunction(){
std::cout << this << " : non const function is called." << std::endl;
}
void ConstFunction() const {
std::cout << this << " : const function is called." << std::endl;
}
};
要素型がnon-referenceの場合
# include <iostream>
# include "X.h"
using namespace std;
int main()
{
X xs[3];
for(auto x : xs)
x.NonConstFunction();
}
ループのたびに、xs
の各要素がx
にコピーされています。すなわち、ループ1周ごとに、コピーコンストラクタがinvoke→デストラクタがinvokeされるようです。
0x7ffee1bef9ac : constructor.
0x7ffee1bef9b0 : constructor.
0x7ffee1bef9b4 : constructor.
0x7ffee1bef9a8 <-- 0x7ffee1bef9ac : copy constructor.
0x7ffee1bef9a8 : non const function is called.
0x7ffee1bef9a8 : destructor.
0x7ffee1bef9a8 <-- 0x7ffee1bef9b0 : copy constructor.
0x7ffee1bef9a8 : non const function is called.
0x7ffee1bef9a8 : destructor.
0x7ffee1bef9a8 <-- 0x7ffee1bef9b4 : copy constructor.
0x7ffee1bef9a8 : non const function is called.
0x7ffee1bef9a8 : destructor.
0x7ffee1bef9b4 : destructor.
0x7ffee1bef9b0 : destructor.
0x7ffee1bef9ac : destructor.
要素型がlvalue referenceの場合
# include <iostream>
# include "X.h"
using namespace std;
int main()
{
X xs[3];
for(auto& x : xs)
x.NonConstFunction();
}
ループのたびに配列xs
の各要素がx
に束縛されますので、コンストラクタはinvokeされない。
0x7ffed7fdecf4 : constructor.
0x7ffed7fdecf8 : constructor.
0x7ffed7fdecfc : constructor.
0x7ffed7fdecf4 : non const function is called.
0x7ffed7fdecf8 : non const function is called.
0x7ffed7fdecfc : non const function is called.
0x7ffed7fdecfc : destructor.
0x7ffed7fdecf8 : destructor.
0x7ffed7fdecf4 : destructor.
要素型がrvalue referenceの場合
# include <iostream>
# include "X.h"
using namespace std;
int main()
{
X xs[3];
for(auto&& x : xs)
x.NonConstFunction();
}
lvalue referenceの場合と同様となるようです(perfect forwardingというのかな)。
0x7ffd963a4db4 : constructor.
0x7ffd963a4db8 : constructor.
0x7ffd963a4dbc : constructor.
0x7ffd963a4db4 : non const function is called.
0x7ffd963a4db8 : non const function is called.
0x7ffd963a4dbc : non const function is called.
0x7ffd963a4dbc : destructor.
0x7ffd963a4db8 : destructor.
0x7ffd963a4db4 : destructor.
xvalueの各要素をrvalue referenceで束縛
# include <iostream>
# include <cstdlib>
# include "X.h"
using namespace std;
struct X2 {
X xs[3];
} x2;
X2 f()
{
return x2;
}
int main()
{
for(auto&& x : f().xs)
x.NonConstFunction();
}
こういうの(xvalueの式が指し示す配列型オブジェクトの各要素をイテレーション)はどうかな?というと、ループ開始前に、f().xs
が指し示す配列型一時オブジェクトの全要素のコピーコンストラクタがinvokeされています。
- ループ開始時の式
f().xs
の評価により、同式が指し示す一時オブジェクトがコピーコンストラクタにより構築される。 - 式
f().xs
は(式f()
がprvalueなのでそのメンバアクセス演算子の式.xs
は)xvalueとなる。 - 式
f().xs
が指し示す一時オブジェクトの各要素はx
により束縛される。
0x601bf1 : constructor.
0x601bf2 : constructor.
0x601bf3 : constructor.
0x7ffc9966c54d <-- 0x601bf1 : copy constructor.
0x7ffc9966c54e <-- 0x601bf2 : copy constructor.
0x7ffc9966c54f <-- 0x601bf3 : copy constructor.
0x7ffc9966c54d : non const function is called.
0x7ffc9966c54e : non const function is called.
0x7ffc9966c54f : non const function is called.
0x7ffc9966c54f : destructor.
0x7ffc9966c54e : destructor.
0x7ffc9966c54d : destructor.
0x601bf3 : destructor.
0x601bf2 : destructor.
0x601bf1 : destructor.
xvalueの各要素をlvalue referenceで束縛
コンパイルエラーが出るだろうなと思い、試しにやってみると。
# include <iostream>
# include <cstdlib>
# include "X.h"
using namespace std;
struct X2 {
X xs[3];
} x2;
X2 f()
{
return x2;
}
int main()
{
for(auto& x : f().xs)
x.NonConstFunction();
}
あれ、xvalueの各要素への束縛なのに、lvalue referenceでも束縛できてしまう?(f().xs
の代わりに型std::vector<bool>
をもつ式だと、コンパイルエラーが出るんですが)。
0x601bf1 : constructor.
0x601bf2 : constructor.
0x601bf3 : constructor.
0x7ffdd740c0bd <-- 0x601bf1 : copy constructor.
0x7ffdd740c0be <-- 0x601bf2 : copy constructor.
0x7ffdd740c0bf <-- 0x601bf3 : copy constructor.
0x7ffdd740c0bd : non const function is called.
0x7ffdd740c0be : non const function is called.
0x7ffdd740c0bf : non const function is called.
0x7ffdd740c0bf : destructor.
0x7ffdd740c0be : destructor.
0x7ffdd740c0bd : destructor.
0x601bf3 : destructor.
0x601bf2 : destructor.
0x601bf1 : destructor.
最後に
コンパイルエラーが出ない、だけでは合法か違法か判断つかないので、ちょっと規格票読んできます。何か分かったら追記します。