这里是你的方法的签名:
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);
问题出现在这里。首先,免费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)); }
其中凸点误差达1个步骤中使用SFINAE解析堆栈。 (注意,在该T&&
和&&
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;
}
我这个玩上live work space这里是输出:
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*
实际上会创建一个新对象。通过使用AUTO
和COPY
突出显示了昂贵的操作(创建新对象和复制) - 如您所见,存在最初的4个字母表对象,表达式中的tmp
对象以及operator*(double)
创建的两个副本。
我们可以摆脱一些副本与此:
a = b + std::move(a) * 4 + std::move(d) * 2 + (A("tmp") + c) * 5;
然而,我们还是最终用不平凡的状态3个物体破坏,因为两次我们做operator+(A&&, A&&)
,而我却没有假定这个操作是高效的。
如果是,我们可以添加该运营商:
A &operator+=(A &&rhs) { recurse_nl _; std::cout << "this{"<<name<<"} += A&&{"<<rhs.name<<"}"; return ((*this)+="(+=")+=std::move(rhs.name)+")"; }
,并输出结果显示,只有一个不平凡的状态对象是不断破坏。
实时工作区的最终版本是here。对于递归跟踪,在基本级别,它在函数的末尾打印一个换行符,在更深的递归操作中,它执行-->
打印,理论上,如果递归深度足够深,它将打印[
括号来帮助)。
最终输出:
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)(*#))}
在这里你可以看到在最后摧毁了一个“复杂的对象”(其整个历史一起)。
@Chameleon:我创建了一个库来帮助你(和其他人),看到更新后的答案。 – 2013-03-24 15:32:09
图书馆4h:很快! – 2013-03-24 17:34:12
图书馆设计的缺点是,它只适用于你愿意侵入的'struct'和'class'类型。如果你通过SFINAE模板和traits类来完成,你可以用'enum'和'枚举类型(对于位域),对于其他类型非侵入式(重写'A + = B'和'B + = A',然后说它是可交换的,而bob是你的叔叔)。缺点是你最终注入了一堆模板操作符到全局命名空间(因为ADL不能再使用),并且为一些无关的类型生成一些诊断消息很难...... – Yakk 2013-03-24 20:53:19