完美转发和临时对象的范围
Perfect forwarding and the scope of temporary objects
为了实现 *
运算符的完美转发,我构建了以下示例。
#include <string>
#include <iostream>
class A {
public:
std::string name;
A(const A& _other) : name(_other.name) {
std::cout << "Copy-Construct with name: " << name << std::endl;
}
A(A&& _other) : name(std::move(_other.name)) {
std::cout << "Move-Construct with name: " << name << std::endl;
}
A(std::string _name): name(_name) { }
};
A operator*(const A& _lhs, const A& _rhs) {
std::cout << "Start Operator Copy with: " << _lhs.name << " " << _rhs.name << std::endl;
A bla(_lhs.name+" "+_rhs.name);
return bla;
}
A&& operator*(A&& _lhs, const A& _rhs) {
std::cout << "Start Operator Move with: " << _lhs.name << " " << _rhs.name << std::endl;
_lhs.name += " "+_rhs.name;
return std::move(_lhs);
}
int main() {
A a("a");
A b("b");
A c("c");
A d("d");
A x = a*b*A("t1")*c*A("t2")*A("t3")*d;
std::cout << "Final result is: " << x.name << std::endl;
}
结果如我所愿,特别是只有一个移动构造函数,没有调用复制构造函数。
Start Operator Copy with: a b
Start Operator Move with: a b t1
Start Operator Move with: a b t1 c
Start Operator Move with: a b t1 c t2
Start Operator Move with: a b t1 c t2 t3
Start Operator Move with: a b t1 c t2 t3 d
Move-Construct with name: a b t1 c t2 t3 d
Final result is: a b t1 c t2 t3 d
现在我的问题是:这是合法的 C++11
代码吗?特别是我是否可以依赖第一个临时对象(由 a 和 b 构造)在分号处而不是在此之前离开其范围这一事实? return 作为移动参考返回作为移动参考获得的对象的构造是否合法?
A&& operator*(const A& _lhs, const A& _rhs) {
std::cout << "Start Operator Copy with: " << _lhs.name << " " << _rhs.name << std::endl;
A* bla = new A(_lhs.name+" "+_rhs.name);
return std::move(*bla);
}
这创建了一个动态分配的对象,因此调用者负责删除它。您的示例无法做到这一点,因此会泄漏内存。这是一个糟糕的功能。它应该 return 按值代替,这样会更快,因为您不在堆上分配对象。
A&& operator*(A&& _lhs, const A& _rhs) {
std::cout << "Start Operator Move with: " << _lhs.name << " " << _rhs.name << std::endl;
_lhs.name += " "+_rhs.name;
return std::move(_lhs);
}
这不会导致内存泄漏,所以不像第一个那样明显是完全错误的,但它仍然是错误的。如果你用一个临时对象调用它,它 return 是对同一个临时对象的引用,但这会导致悬空引用:
A&& c = A("a") * A("b");
引用 c
绑定到 A("a")
创建的临时对象,但在语句末尾超出了范围。任何使用 c
的尝试都有未定义的行为。
两个重载都应该 return 按值。
对于左侧是左值而右侧是右值的情况,您可能还需要重载,因为这样您就可以重新使用右侧的对象。如果你补充说,你还需要一个重载来处理两个操作数都是右值的情况。基本上,看看 std::string
如何定义 operator+
为了实现 *
运算符的完美转发,我构建了以下示例。
#include <string>
#include <iostream>
class A {
public:
std::string name;
A(const A& _other) : name(_other.name) {
std::cout << "Copy-Construct with name: " << name << std::endl;
}
A(A&& _other) : name(std::move(_other.name)) {
std::cout << "Move-Construct with name: " << name << std::endl;
}
A(std::string _name): name(_name) { }
};
A operator*(const A& _lhs, const A& _rhs) {
std::cout << "Start Operator Copy with: " << _lhs.name << " " << _rhs.name << std::endl;
A bla(_lhs.name+" "+_rhs.name);
return bla;
}
A&& operator*(A&& _lhs, const A& _rhs) {
std::cout << "Start Operator Move with: " << _lhs.name << " " << _rhs.name << std::endl;
_lhs.name += " "+_rhs.name;
return std::move(_lhs);
}
int main() {
A a("a");
A b("b");
A c("c");
A d("d");
A x = a*b*A("t1")*c*A("t2")*A("t3")*d;
std::cout << "Final result is: " << x.name << std::endl;
}
结果如我所愿,特别是只有一个移动构造函数,没有调用复制构造函数。
Start Operator Copy with: a b
Start Operator Move with: a b t1
Start Operator Move with: a b t1 c
Start Operator Move with: a b t1 c t2
Start Operator Move with: a b t1 c t2 t3
Start Operator Move with: a b t1 c t2 t3 d
Move-Construct with name: a b t1 c t2 t3 d
Final result is: a b t1 c t2 t3 d
现在我的问题是:这是合法的 C++11
代码吗?特别是我是否可以依赖第一个临时对象(由 a 和 b 构造)在分号处而不是在此之前离开其范围这一事实? return 作为移动参考返回作为移动参考获得的对象的构造是否合法?
A&& operator*(const A& _lhs, const A& _rhs) {
std::cout << "Start Operator Copy with: " << _lhs.name << " " << _rhs.name << std::endl;
A* bla = new A(_lhs.name+" "+_rhs.name);
return std::move(*bla);
}
这创建了一个动态分配的对象,因此调用者负责删除它。您的示例无法做到这一点,因此会泄漏内存。这是一个糟糕的功能。它应该 return 按值代替,这样会更快,因为您不在堆上分配对象。
A&& operator*(A&& _lhs, const A& _rhs) {
std::cout << "Start Operator Move with: " << _lhs.name << " " << _rhs.name << std::endl;
_lhs.name += " "+_rhs.name;
return std::move(_lhs);
}
这不会导致内存泄漏,所以不像第一个那样明显是完全错误的,但它仍然是错误的。如果你用一个临时对象调用它,它 return 是对同一个临时对象的引用,但这会导致悬空引用:
A&& c = A("a") * A("b");
引用 c
绑定到 A("a")
创建的临时对象,但在语句末尾超出了范围。任何使用 c
的尝试都有未定义的行为。
两个重载都应该 return 按值。
对于左侧是左值而右侧是右值的情况,您可能还需要重载,因为这样您就可以重新使用右侧的对象。如果你补充说,你还需要一个重载来处理两个操作数都是右值的情况。基本上,看看 std::string
如何定义 operator+