メソッドのシグネチャは次のとおりです。
struct A
{
A() = default;
A(const A& a);
A(A&& a);
A &operator=(const A& a);
A &operator=(A&& a);
A &operator*=(double s);
A operator*(double s) const;
A &operator+=(const A &b);
A operator+(const A &b) const;
A &operator+(A &&b) const;
};
A &operator+(A &&a, const A &b);
A &operator*(A &&a, double s);
ここで問題が発生します。まず、右辺値参照が左辺値に変更されないように、 free は渡された をoperator+
返す必要があります。A&&
同じことが当てはまりますA &A::operator+(A &&b) const;
- を返す必要がありA&&
ます。
次に、無料の演算子が演算子に連鎖してい+=
ます。ここにかわいいテクニックがあります:
template<typename T>
A&&operator+(A &&a, T&&b){ return std::move(a+=std::forward<T>(b)); }
template<typename T>
A&&operator*(A &&a, T&&b){ return std::move(a*=std::forward<T>(b)); }
ここで、引数を操作に盲目的に転送します+=
。
auto
これは、戻り値の手法を使用して、エラーに関してより堅牢にすることができます。
template<typename T>
auto operator+(A &&a, T&&b)->declval(std::move(a+=std::forward<T>(b)))
{ return std::move(a+=std::forward<T>(b)); }
template<typename T>
auto operator*(A &&a, T&&b)->declval(std::move(a*=std::forward<T>(b)))
{ return std::move(a*=std::forward<T>(b)); }
これは、SFINAE を使用して解析スタックでエラーを 1 ステップ上に上げます。&&
( inT&&
とA&&
はまったく異なる意味を持つことに注意してください--は型推論コンテキストで使用されているため、任意の参照型にバインドできT&&
ますが、 は型推論コンテキストでは使用されていないため、にバインドすることを意味します右辺値)。&&
T
A&&
&&
A&&
次に続くのは、正確さと効率の両方のためにいくつかの基本的な変更を加えた、はるかに大幅にマークアップされたバージョンです。フィールド内の各インスタンスの履歴を追跡します。name
このフィールドの操作は「本物」ではなく、その値は特定のインスタンスを作成するために必要な「計算」を表します。
移動操作はこの状態を移動すると仮定します。
#include <iostream>
#include <utility>
struct A;
A &operator+=(A& a, std::string op);
A&&operator+=(A&& a, std::string op);
struct recurse_nl {
int& count() {
static int v = 0;
return v;
}
recurse_nl(){if (++count()>1) std::cout << " --> "; else if (count()>2) std::cout << " --> [";}
~recurse_nl(){if (--count() == 0) std::cout <<"\n"; else if (count()>1) std::cout << "]"; }
};
struct A
{
std::string name;
A() = delete;
A(std::string n):name(n) { recurse_nl _; std::cout << "AUTO ctor{"<<name<<"}";};
A(const A& o):name(o.name+"_c&") { recurse_nl _; std::cout << "COPY ctor{"<<name<<"}(const&)"; }
A(A&& o):name(std::move(o.name)) { recurse_nl _; std::cout << "ctor{"<<name<<"}(&&)"; }
A(A& o):name(o.name+"_&") { recurse_nl _; std::cout << "COPY ctor{"<<name<<"}(&)"; }
A &operator=(const A& rhs) { recurse_nl _; std::cout << "COPY assign{"<<name<<"}={"<<rhs.name<<"}"; this->name = rhs.name; return *this; }
A &operator=(A&& rhs) { recurse_nl _; std::cout << "move assign{"<<name<<"}={"<<rhs.name<<"}"; this->name = std::move(rhs.name); return *this; }
A &operator*=(double d) { recurse_nl _; std::cout << "this{"<<name<<"} *= s{"<<d<<"}"; return (*this) += "(*#)"; }
A operator*(double d) const { recurse_nl _; std::cout << "A = const this{"<<name<<"} * s{"<<d<<"}"; A tmp(*this); return std::move(tmp*=d); }
A &operator+=(const A &rhs) { recurse_nl _; std::cout << "this{"<<name<<"} += const A&{"<<rhs.name<<"}"; return ((*this)+="(+=")+=rhs.name+")"; }
A operator+(const A &rhs) const { recurse_nl _; std::cout << "A = const this{"<<name<<"} + const A&{"<<rhs.name<<"}"; return std::move(A(*this)+="(+)"); }
A&& operator+(A &&rhs) const { recurse_nl _; std::cout << "A&& = const this{"<<name<<"} + A&&{"<<rhs.name<<"}"; return std::move(rhs += *this); }
~A() { recurse_nl _; std::cout << "dtor{"<<name<<"}"; }
};
A &operator+=(A& a, std::string op)
{ a.name+=op; return a; }
A&&operator+=(A&& a, std::string op)
{ a.name+=op; return std::move(a); }
template<typename T>
struct ref_type_of {
std::string value() const { return "value"; }
};
template<typename T>
struct ref_type_of<T&> {
std::string value() const { return "&"; }
};
template<typename T>
struct ref_type_of<T&&> {
std::string value() const { return "&&"; }
};
template<typename T>
struct ref_type_of<T const&&> {
std::string value() const { return " const&&"; }
};
template<typename T>
struct ref_type_of<T const&> {
std::string value() const { return " const&"; }
};
template<typename T>
std::string ref_type() { return ref_type_of<T>().value(); }
template<typename T>
A&& operator+(A &&a, T&& b) { recurse_nl _; std::cout << "A&&{"<<a.name<<"} = A&&{"<<a.name<<"} + T" << ref_type<T>(); return std::move(a += std::forward<T>(b)); }
template<typename T>
A&& operator*(A &&a, T&& b) { recurse_nl _; std::cout << "A&&{"<<a.name<<"} = A&&{"<<a.name<<"} * T" << ref_type<T>(); return std::move(a *= std::forward<T>(b)); }
void test1()
{
A a("a"),b("b"),c("c"),d("d");
a = b + a * 4 + d * 2 + (A("tmp") + c) * 5;
}
int main()
{
std::cout << "test1\n";
test1();
return 0;
}
私はライブワークスペースでこれを試しましたが、出力は次のとおりです。
stdout:
test1
AUTO ctor{a}
AUTO ctor{b}
AUTO ctor{c}
AUTO ctor{d}
AUTO ctor{tmp}
A&&{tmp} = A&&{tmp} + T& --> this{tmp} += const A&{c}
A&&{tmp(+=c)} = A&&{tmp(+=c)} * Tvalue --> this{tmp(+=c)} *= s{5}
A = const this{d} * s{2} --> COPY ctor{d_c&}(const&) --> this{d_c&} *= s{2} --> ctor{d_c&(*#)}(&&) --> dtor{}
A = const this{a} * s{4} --> COPY ctor{a_c&}(const&) --> this{a_c&} *= s{4} --> ctor{a_c&(*#)}(&&) --> dtor{}
A&& = const this{b} + A&&{a_c&(*#)} --> this{a_c&(*#)} += const A&{b}
A&&{a_c&(*#)(+=b)} = A&&{a_c&(*#)(+=b)} + Tvalue --> this{a_c&(*#)(+=b)} += const A&{d_c&(*#)}
A&&{a_c&(*#)(+=b)(+=d_c&(*#))} = A&&{a_c&(*#)(+=b)(+=d_c&(*#))} + Tvalue --> this{a_c&(*#)(+=b)(+=d_c&(*#))} += const A&{tmp(+=c)(*#)}
move assign{a}={a_c&(*#)(+=b)(+=d_c&(*#))(+=tmp(+=c)(*#))}
dtor{a}
dtor{d_c&(*#)}
dtor{tmp(+=c)(*#)}
dtor{d}
dtor{c}
dtor{b}
dtor{a_c&(*#)(+=b)(+=d_c&(*#))(+=tmp(+=c)(*#))}
これはかなり冗長ですが、ほぼすべての操作を示しています。
operator+
必要に応じてoperator*
実際に新しいオブジェクトを作成するようにコードを変更しました。コストのかかる操作 (新しいオブジェクトの作成とコピー) は、 and を使用して強調表示さAUTO
れCOPY
ます。ご覧のとおり、最初の 4 つのアルファベット オブジェクト、tmp
式内のオブジェクト、および によって作成された 2 つのコピーがありoperator*(double)
ます。
これでいくつかのコピーを取り除くことができます:
a = b + std::move(a) * 4 + std::move(d) * 2 + (A("tmp") + c) * 5;
ただし、2 回行うため、破棄する重要な状態のオブジェクトが 3 つになることoperator+(A&&, A&&)
になり、この操作が非常に効率的であるとは想定していませんでした。
そうであれば、次の演算子を追加できます。
A &operator+=(A &&rhs) { recurse_nl _; std::cout << "this{"<<name<<"} += A&&{"<<rhs.name<<"}"; return ((*this)+="(+=")+=std::move(rhs.name)+")"; }
結果の出力は、非自明な状態のオブジェクトが 1 つだけ破棄されることを示しています。
ライブ ワークスペースの最終バージョンはこちらです。
(recurse_nl
オブジェクトは再帰追跡用です。基本レベルでは、関数の最後に改行を出力します。より深い再帰では、出力を行います。-->
理論的には、再帰が十分に深くなると、[
ブラケットを出力して助けになります)。
最終出力:
test1
AUTO ctor{a}
AUTO ctor{b}
AUTO ctor{c}
AUTO ctor{d}
AUTO ctor{tmp}
A&&{tmp} = A&&{tmp} + T& --> this{tmp} += const A&{c}
A&&{tmp(+=c)} = A&&{tmp(+=c)} * Tvalue --> this{tmp(+=c)} *= s{5}
A&&{d} = A&&{d} * Tvalue --> this{d} *= s{2}
A&&{a} = A&&{a} * Tvalue --> this{a} *= s{4}
A&& = const this{b} + A&&{a(*#)} --> this{a(*#)} += const A&{b}
A&&{a(*#)(+=b)} = A&&{a(*#)(+=b)} + Tvalue --> this{a(*#)(+=b)} += A&&{d(*#)}
A&&{a(*#)(+=b)(+=d(*#))} = A&&{a(*#)(+=b)(+=d(*#))} + Tvalue --> this{a(*#)(+=b)(+=d(*#))} += A&&{tmp(+=c)(*#)}
move assign{a(*#)(+=b)(+=d(*#))(+=tmp(+=c)(*#))}={a(*#)(+=b)(+=d(*#))(+=tmp(+=c)(*#))}
dtor{}
dtor{}
dtor{c}
dtor{b}
dtor{a(*#)(+=b)(+=d(*#))(+=tmp(+=c)(*#))}
最後に、単一の「複雑なオブジェクト」が (その全履歴と共に) 破棄されているのを確認できます。