13 拷贝控制
拷贝控制操作 包含 拷贝构造函数, 拷贝赋值运算符, 移动构造函数, 移动赋值运算符, 析构函数
拷贝和移动构造函数定义了 当用同类型的另一个对象初始化对象时做什么.
拷贝和移动运算符定义了一个对象赋予同类型的另一个对象时做什么
13.1 拷贝 赋值与销毁
13.1.1 拷贝构造函数
拷贝构造函数的: 构造函数的一种, 该构造函数的第一个参数是自身类类型的引用 且 任何额外参数都有默认值
合成拷贝构造函数: 编译器自动创建的一种拷贝构造函数.
拷贝初始化不仅仅发生在=时 还有可能:
- 将一个对象作为实参传递给一个非引用类型的形参时
- 从一个返回类型为非引用类型的函数返回一个对象时
- 用花括号列表初始化一个数组中的元素或一个聚合类中的成员时
参数和返回值
13.1.2 拷贝赋值运算符
重载赋值运算符: 其本质是函数, 其名字由operator关键字后接表示要定义的运算符的符号组成.
所以 赋值运算符 就是一个名为operator=的函数.
为了与内置类型的赋值保持一致, 赋值运算符通常返回一个指向其左侧运算对象的引用. 如
Foo& operator= (const Foo&);
合成拷贝赋值运算符: 如果类未定义自己的拷贝赋值运算符, 编译器会生成一个合成拷贝赋值运算符.
13.1.3 析构函数
用于销毁类内所有非static数据成员. 特点:
- 名字由波浪线接类名构成
- 没有返回值
- 不接受参数
- 不能被重载
- 一个类只会有一个析构函数
- 销毁成员的顺序是按照初始化顺序的逆序进行
- 内置类型没有析构函数, 销毁内置类型成员什么也不做
无论何时, 一个对象被销毁 就会自动调用其析构函数:
- 变量离开其作用域时
- 对象被销毁时, 其成员也会被销毁
- 容器被销毁时, 其元素也会被销毁
- 动态分配的对象调用 delete时
- 临时对象, 当创建它的完整表达式结束时被销毁
当指向一个对象的引用或指针离开作用域时, 析构函数不会执行. 个人理解: 如果此时调用析构函数, 则引用或者指针 指向的对象被销毁, 如果作用域外仍然有指向该对象的引用或指针, 则对象已经被销毁了, 届时它们将变成不安全的指向.
合成析构函数
如果合成的析构函数体是空的, 则成员的析构是在析构函数执行结束后被销毁.
struct X {
X() { std::cout << "X()" << std::endl; }
X(const X&) { std::cout << "X(const X&)" << std::endl; }
X& operator=(const X& ori) {
std::cout << " operator= " << std::endl;
return *this;
}
~X() {
std::cout << "~ X() " << std::endl;
}
};
void xtest(X arg_x) {
std::cout << "xing can" << std::endl;
}
void xtest2(X& r_arg_x) {
std::cout << "xtest2" << std::endl;
}
int main(int argc, char** argv) {
X x = X();
// xtest(x);
//X()
//X(const X&)
//~X()
//~X()
//xtest2(x);
//X()
//~X()
//auto newX1 = new X();
//delete newX1;
//X()
//~X()
//vector<X> vec(1);
//vec.push_back(x);
//X()
//X()
//X(const X&)
//X(const X&)
//~X()
//~X()
//~X()
//~X()
}
13.1.4 三/五法则
三个基本操作: 拷贝构造函数, 拷贝赋值运算符, 析构函数, 还可以定义一个移动构造函数, 一个移动赋值运算符.
如果一个类需要自定义析构函数, 几乎可以肯定它也需要自定义拷贝赋值运算符和拷贝构造函数, 可以通过下面的函数理解上面的话:
HasPtr f(HasPtr hp) {
HasPtr ret = hp; // 如果使用合成拷贝控制运算符, 则ps会指向相同的对象
return ret; // 当return后 ret, 和hp都会调用各自的析构函数, 而析构函数会销毁*p, 两次调用delete会报错.
}
需要拷贝操作的类也需要赋值操作, 反之依然
如果一个类需要一个拷贝构造函数, 几乎可以肯定它也需要一个拷贝赋值运算符, 反之依然. 但不一定需要析构函数
这两个原则简写为:
如果需要析构, 则几乎必须自定义拷贝赋值运算符和拷贝构造函数.
如果需要拷贝赋值运算符则几乎肯定需要拷贝构造,反之依然, 不必 析构函数
拷贝构造和拷贝赋值 几乎成对出现.
class numbered {
public:
numbered() {
mysn = "abc:" + getRandomNumbers();
};
string mysn;
string getRandomNumbers() {
int z = (int)GetTickCount64();
cout << "z: " << z << endl;
srand(z);
string ss = "";
for (int i = 0; i < 10; i++)
{
ss += to_string(rand() % 100);
}
cout << ss << " ";
return ss;
}
};
void f1(numbered s) { cout << s.mysn << endl; }
int main(int argc, char** argv) {
numbered a, b = a, c = b;
f1(a);
cout << "f1(a)" << endl;
f1(b);
cout << "f1(b)" << endl;
f1(c);
cout << "f1(c)" << endl;
}
// 输出的所有mysn 结果都是相同的
// 13.15
class numbered2 {
public:
numbered2() {
mysn = "abc:" + getRandomNumbers();
};
numbered2(const numbered2& ori) {
mysn = getRandomNumbers();
};
string mysn;
string getRandomNumbers() {
int z = (int)GetTickCount64();
cout << "z: " << z << endl;
srand(z);
string ss = "";
for (int i = 0; i < 10; i++)
{
ss += to_string(rand() % 100);
}
cout << ss << " ";
return ss;
}
};
void f2(numbered2 s) { cout << s.mysn << endl; }
int main(int argc, char** argv) {
// 13.15
//numbered2 a, b = a, c = b;
//f2(a);
//cout << "f2(a)" << endl;
//f2(b);
//cout << "f2(b)" << endl;
//f2(c);
//cout << "f2(c)" << endl;
}
// 所有的mysn 都不相同
13.1.5 使用=default
用于显式的要求编译器生成合成控制函数.
Sales_data() = default;
Sales_data(const Sales_data&) = default;
Sales_data& operator=(const Sales_data &);
~Sales_data() = default;
使用=default在类内时, 默认生成是内联函数, 如果不想使用内联函数, 则应在类外使用=default
13.1.6 阻止拷贝 =delete
删除的函数: 虽然声明了函数,但是不能以任何方式使用.使用=delete表示.
=delete必须出现在函数第一次声明的时候.
析构函数不能是删除的成员 否则将无法释放对象.
合成的拷贝控制成员可能是删除的
- 如果一个类有数据成员不能默认构造,拷贝,复制和销毁, 则对应的成员函数将被定义为删除的
- 如果一个类有const成员, 则它不能使用合成的拷贝赋值运算符.
- 对于有引用成员的类, 合成拷贝运算符被定义为删除的.
private 拷贝控制 老标准中组织拷贝的方法, 现在应该使用=delete
13.2 拷贝控制和资源管理
类值行为, 类指针行为.
13.2.1 行为像值的类
定义赋值运算符时, 安全的方法是: 先定义一个临时变量用于保存右侧值, 然后再销毁左侧对象, 最后就是把临时对象赋值给左侧对象.
auto newValue = new string(*ori.ps);
delete ps;
ps = newValue;
13.2.2 行为像指针的类
引用计数: 用于实现 shared_ptr的自定义版本. 引用计数使用一块动态内存记录对对象的副本个数.
// 13.28
class TreeNode {
TreeNode(std::string s = std::string()) : value(s), cout(new int(1)), left(nullptr), right(nullptr) { };
TreeNode(const TreeNode& rhi) :value(rhi.value), cout(rhi.cout), left(rhi.left), right(rhi.right) { ++* cout;};
TreeNode& operator= (const TreeNode &rhi){
++* rhi.cout;
if (-- * cout == 0) {
if (left) { delete left; }
if (right) { delete right; }
}
value = rhi.value;
cout = rhi.cout;
left = rhi.left;
right = rhi.right;
return *this;
};
~TreeNode() {
if (--*cout == 0) {
if (left) {
delete left;
left = nullptr;
}
if (right) {
delete right;
right = nullptr;
}
delete cout;
cout = nullptr;
}
};
private:
std::string value;
int* cout;
TreeNode* left;
TreeNode* right;
};
class BinStrTree {
public:
BinStrTree():root(new TreeNode()) { };
BinStrTree(const BinStrTree& rhs) :root(new TreeNode(*rhs.root)) {};
BinStrTree& operator=(const BinStrTree& rhs) {
auto tmp = new TreeNode(*rhs.root);
delete root;
root = tmp;
return *this;
};
~BinStrTree() {
if (root) {
delete root;
}
}
private:
TreeNode* root;
};
13.3 交换操作
如果类内存在自定义版本的swap, 则优先使用类内定义的swap, 否则将使用std::swap.
在赋值运算符中使用swap
HasPtr& HasPtr::operator=(HasPtr rhs) { // 注意此处的参数值, 不再是const HasPtr &rhs
swap(*this, rhs);
return *this;
}
上面代码将this指向的是rhs的副本, 而后rhs被销毁
13.4 拷贝控制示例
// 13.34 13.36 13.37
#pragma once
#include <string>
#include <set>
class Folder;
class Message {
friend class Folder;
public:
friend void swap(Message&, Message&);
explicit Message(const std::string& str = "") :contents(str) {}
Message(const Message&); // 拷贝构造函数
Message& operator=(const Message&); //拷贝赋值运算符
~Message(); // 析构函数
void save(Folder&); // 从给定的folder集合中添加message
void remove(Folder&); //从给定的folder集合中删除message
void print_for_dubug() {
std::cout << "Message: " << contents << std::endl;
}
private:
std::string contents; // 实际消息文本
std::set<Folder*> folders; // 包含本message的folder
void add_to_Folders(const Message&); // 将本message添加到指向参数的folder中
void remove_from_Folders();
void addFloder(Folder* f) { folders.insert(f); }
void removeFloder(Folder* f) { folders.erase(f); }
};
class Folder {
friend class Message;
public:
friend void swap(Folder&, Folder&);
Folder() = default;
Folder(const Folder&);
Folder& operator=(const Folder&);
~Folder();
void addMsg(Message*);
void remMsg(Message*);
void print_for_dubug() {
for(auto m : messages) std::cout << "Message: " << m->contents << std::endl;
}
private:
void remove_from_message();
void add_to_message(const Folder&);
std::set<Message*> messages;
};
void Message::save(Folder& f) {
folders.insert(&f); // 将给定的folder添加到message的folders列表中
f.addMsg(this); // 将message 添加到folder的message中
}
void Message::remove(Folder& f) {
folders.erase(&f);// 在message的folders列表中删除给定的folder
f.remMsg(this); // 在给定的folder中的message列表中删除该messge
}
void Message::add_to_Folders(const Message& m) {
// 拷贝message时, 需要在该message中指向的folder中拷贝.
for (auto f : m.folders)
f->addMsg(this);
}
void Message::remove_from_Folders() {
for (auto f : folders)
f->remMsg(this);
}
Message::Message(const Message& m) : contents(m.contents), folders(m.folders) {
// 拷贝构造函数
add_to_Folders(m);
}
Message& Message::operator=(const Message& m) {
remove_from_Folders(); // 先删除左侧Message 中 所有的folder记录
contents = m.contents;
folders = m.folders;
add_to_Folders(m); // 将新message添加到m的folder所指向的folder中
return *this;
}
Message::~Message() {
remove_from_Folders();
}
void swap(Message& lhs, Message& rhs) {
using std::swap;
for (auto f : lhs.folders)
f->remMsg(&lhs);
for (auto f : rhs.folders)
f->remMsg(&rhs);
swap(lhs.folders, rhs.folders);
swap(lhs.contents, rhs.contents);
for (auto f : lhs.folders)
f->addMsg(&lhs);
for (auto f : rhs.folders)
f->addMsg(&rhs);
}
void Folder::addMsg(Message* m) {
messages.insert(m);
}
void Folder::remMsg(Message* m) {
messages.erase(m);
}
void Folder::remove_from_message() {
for (auto m : messages)
m->removeFloder(this);
}
void Folder::add_to_message(const Folder& f) {
for (auto m : messages)
m->addFloder(this);
}
Folder::Folder(const Folder& f):messages(f.messages) {
add_to_message(f);
}
Folder& Folder::operator=(const Folder& f) {
remove_from_message();
messages = f.messages;
add_to_message(f);
return *this;
}
Folder::~Folder() {
remove_from_message();
}
void swap(Folder& f1, Folder& f2) {
f1.remove_from_message();
f2.remove_from_message();
std::swap(f1.messages, f2.messages);
f1.add_to_message(f1);
f2.add_to_message(f2);
}
int main(int argc, char** argv) {
//13.36 13.37
Message firstMail("firstMail");
Message secondMail("secondMail");
Folder mailBox1, mailBox2;
cout << "firstMail & secondMail debug message" << endl;
firstMail.print_for_dubug();
secondMail.print_for_dubug();
firstMail.save(mailBox1);
secondMail.save(mailBox1);
cout << "mailBox1 debug message" << endl;
mailBox1.print_for_dubug();
firstMail.save(mailBox2);
secondMail.save(mailBox2);
cout << "mailBox2 debug message" << endl;
mailBox2.print_for_dubug();
firstMail = secondMail;
cout << "mailBox1 debug message" << endl;
mailBox1.print_for_dubug();
cout << "mailBox2 debug message" << endl;
mailBox2.print_for_dubug();
}
13.5 动态内存管理类
#pragma once
#include<string>
#include<initializer_list>
#include<algorithm>
class StrVec {
public:
StrVec() :elements(nullptr), first_free(nullptr), cap(nullptr) {}
StrVec(const StrVec&);
StrVec(const std::initializer_list<std::string>&);
StrVec& operator=(const StrVec&);
~StrVec();
void push_back(const std::string&);
size_t size() const { return first_free - elements; }
size_t capacity() const { return cap - elements; }
void resize(size_t);
void resize(size_t, const std::string&);
// 若 new_cap 大于当前的 capacity() ,则分配新存储,否则该方法不做任何事。
void reserve(size_t); // 增加vector的容量到大于或者等于 指定的值
std::string* begin() const { return elements; }
std::string* end() const { return first_free; }
private:
static std::allocator<std::string> alloc; // 用于分配元素内存
// 如果没有空间容纳新元素了, 则重新分配内存
void chk_n_alloc() {
if (size() == capacity()) reallocate();
}
// 用于分配足够的内存保存给定范围的元素, 并将这些元素拷贝到新分配的内存中.返回的pair中的两个指针
// 分别指向新空间的开始位置和尾后位置.
std::pair<std::string*, std::string*>alloc_n_copy(const std::string*, const std::string*);
void free(); // 销毁元素并释放内存
void reallocate(size_t); // 获得更多内存并拷贝已有元素
std::string* elements; // 指向首元素的指针
std::string* first_free; // 指向第一个空闲元素的指针
std::string* cap; // 指向尾后位置的指针
};
void StrVec::push_back(const std::string& s) {
chk_n_alloc();
alloc.construct(first_free++, s);
}
std::pair<std::string*, std::string*>StrVec::alloc_n_copy(const std::string* b, const std::string* e) {
auto data = alloc.allocate(e - b);
return { data, uninitialized_copy(b,e, data) };
}
void StrVec::free() {
// elements 如果为空 说明该vec为空, 则不必释放内存
if (elements) {
for (auto p = first_free; p != elements;)
alloc.destroy(--p);
alloc.deallocate(elements, cap - elements);
}
// 13.43
if (elements) {
std::for_each(elements, first_free, [this](std::string& s) { alloc.destroy(&s); });
alloc.deallocate(elements, cap - elements);
}
}
StrVec::StrVec(const StrVec& s) {
auto newData = alloc_n_copy(s.begin(), s.end());
elements = newData.first;
first_free = cap = newData.second;
}
StrVec::~StrVec() {
free();
}
StrVec& StrVec::operator=(const StrVec& rhs) {
auto data = alloc_n_copy(rhs.begin(), rhs.end());
free();
elements = data.first;
first_free = cap = data.second;
return * this;
}
void StrVec::reallocate(size_t new_size) {
size_t newcapacity = 0;
if (new_size == 0) newcapacity = size() ? 2 * size() : 1;
else newcapacity = new_size;
auto newdata = alloc.allocate(newcapacity);
auto dest = newdata;
auto elem = elements;
for (size_t i = 0; i != size(); ++i)
alloc.construct(dest++, std::move(*elem++));
free();
elements = newdata;
first_free = dest;
cap = elements + newcapacity;
}
// 13.39
void StrVec::reserve(size_t new_cap) {
if (new_cap > capacity()) {
reallocate(new_cap);
}
}
void StrVec::resize(size_t count) { resize(count, std::string()); }
void StrVec::resize(size_t count, const std::string& s) {
if (count > size()) {
if (count > capacity()) reallocate(2 * count);
for (size_t t = size(); t != count; ++t) {
alloc.construct(first_free++, s);
}
}
else if(count < size()){
while (first_free != elements + count) alloc.destroy(--first_free);
}
}
// 13.40
StrVec::StrVec(const std::initializer_list<std::string>&lst) {
auto newdata = alloc_n_copy(lst.begin(), lst.end());
elements = newdata.first;
first_free= cap = newdata.second;
}
#pragma once
#include <string>
#include <algorithm>
#include <memory>
// 13.44
class String_1344 {
public:
String_1344() :elements(nullptr), end(nullptr) {};
String_1344(const char*);
String_1344(const String_1344&);
String_1344& operator=(const String_1344&);
~String_1344();
void print_content_for_debug() {
std::ostream_iterator<char> out_iter(std::cout, " ");
std::copy(elements, end, out_iter);
std::cout << endl;
}
private:
void free();
std::allocator<char> alloc;
char* elements;
char* end;
std::pair<char*, char*>alloc_n_copy(const char*, const char*);
};
void String_1344::free() {
if (elements) {
std::for_each(elements, end, [this](char& c) { alloc.destroy(&c); });
alloc.deallocate(elements, end-elements);
}
}
String_1344::~String_1344() {
free();
}
std::pair<char*, char*>String_1344::alloc_n_copy(const char* b, const char* e) {
auto data = alloc.allocate(e - b);
return { data, std::uninitialized_copy(b,e, data) };
}
String_1344::String_1344(const char* c) {
auto c_begin = const_cast<char*>(c);
auto c_end = c_begin + strlen(c);
auto r = alloc_n_copy(c_begin, c_end);
elements = r.first;
end = r.second;
}
String_1344::String_1344(const String_1344&s) {
auto p = alloc_n_copy(s.elements, s.end);
elements = p.first;
end = p.second;
}
String_1344& String_1344::operator=(const String_1344& rhs) {
auto data = alloc_n_copy(rhs.elements, rhs.end);
free();
elements = data.first;
end = data.second;
return *this;
}
int main(int argc, char** argv) {
// 13.44
const char* c1 = "abceefgsdfadsf";
const char* c2 = "eeeeeee";
const char* c3 = "AAABBBCCC";
String_1344 s(c1);
String_1344 s2(c2);
s.print_content_for_debug();
s2.print_content_for_debug();
s = s2;
s.print_content_for_debug();
String_1344 s3(c3);
s3.print_content_for_debug();
String_1344 s4(s3);
s4.print_content_for_debug();
}
13.6 对象移动
标准库容器, string和shared_ptr 既支持移动也支持拷贝, IO类和unique_ptr可以移动但不能拷贝
13.6.1 右值引用
必须绑定到右值的引用, 符号&&可以获得右值引用. 一个重要的性质: 只能绑定到一个将要销毁的对象.
左值持久, 右值短暂: 左值有持久的状态, 而右值要么是字面常量, 要么是在表达式求值过程中创建的临时对象.
特性:
- 所引用的对象将要被销毁
- 该对象没有其他用户
- 使用右值引用的代码可以自由的接管所引用的对象的资源
- 不能将一个右值引用绑定到一个右值引用类型的变量上
int &&rr1 = 42; // 正确的 字面值常量是右值
int &&rr2 = rr1; // 错误的, 表达式rr1 是左值
int &&rr3 = std::move(rr1); // 正确
变量是左值, 因此不能将一个右值引用直接绑定到一个变量上, 即使是这个变量是右值引用类型的也不行, 可以使用move函数
13.6.2 移动构造函数和移动赋值运算符
noexcept : 承诺函数不抛出异常, 声明和定义 中都需要 使用noexcept. 位于参数列表的括号后面.
StrVec::StrVec(StrVec&& s) noexcept :elements(s.elements), first_free(s.first_free), cap(s.cap) {
s.elements = s.first_free = s.cap = nullptr;
}
StrVec& StrVec::operator=(StrVec&& rrs) noexcept {
if (this != &rrs) {
free();
elements = rrs.elements;
first_free = rrs.first_free;
cap = rrs.cap;
rrs.elements = rrs.first_free = rrs.cap = nullptr;
}
return *this;
};
只有当一个类没有定义任何的拷贝控制成员, 且它的所有数据成员都能移动构造或移动赋值, 编译器才会为它合成移动构造函数或移动赋值运算符.
移动操作永远不会隐式定义为删除的函数.
如果一个类定义了一个移动构造函数和 或 一个移动赋值运算符的话, 则该类的合成拷贝构造函数和合成赋值运算符会被定义为删除的.
如果一个类有拷贝构造函数而没有移动构造函数的话, 编译器不会自动合成, 则拷贝构造函数会被调用,即使是调用move函数.
即: Foo&& 可以转化为const Foo&
关于std::move
string a = "abcdefg";
vector<string> vec;
vector<string> vec2;
vec.push_back(a);
cout << "a1: " << a << endl;
vec2.push_back(std::move(a));
cout << "a2: " << a << endl; // a2: 为空, 因为a2的值 被move进了vec2
ostream_iterator<string> out_iter(cout, " ");
copy(vec.begin(), vec.end(), out_iter);
copy(vec2.begin(), vec2.end(), out_iter);
移动迭代器: 一个迭代器的解引用运算符返回一个指向元素的左值, 而移动迭代器解引用运算符生成一个右值引用. make_move_iterator(iterator) 普通迭代器转化为移动迭代器.
右值引用, 绝大多数用于移动, 而移动后的移后源都是不确定的, 如果任然后使用移后源的情况的话 就不要使用移动操作
// String类中
String_1344& String_1344::operator=(String_1344&& rhs)noexcept {
std::cout << "String_1344& String_1344::operator=(String_1344&& rhs)" << endl;
if (this != &rhs) {
free();
elements = std::move(rhs.elements);
end = std::move(rhs.end);
rhs.elements = rhs.end = nullptr;
}
return *this;
};
String_1344::String_1344(String_1344&& rhs) noexcept :elements(std::move(rhs.elements)), end(std::move(rhs.end)) {
std::cout << "String_1344::String_1344(String_1344&& rhs)" << endl;
rhs.elements = rhs.end = nullptr;
}
// main中
// 13.50
vector<String_1344> vec;
String_1344 a("abc");
String_1344 b("cde");
String_1344 c("hhh");
cout << "begin push_back" << endl;
vec.push_back(std::move(a));
cout << "mid push_back" << endl;
vec.push_back(std::move(b));
cout << "end push_back" << endl;
vec.push_back(std::move(c));
/* 输出
begin push_back
String_1344::String_1344(String_1344 && rhs)
mid push_back
String_1344::String_1344(String_1344 && rhs)
String_1344::String_1344(String_1344 && rhs)
end push_back //第三次push_back, 移动了前两次的
String_1344::String_1344(String_1344 && rhs)
String_1344::String_1344(String_1344 && rhs)
String_1344::String_1344(String_1344 && rhs)
*/
13.6.3 右值引用和成员函数
引用限定符: &
string s1 = "abc", s2="cde";
s1+s2 = "hello"; //编译器不会报错,旧的标准中无法阻止这种赋值方式, 新标准中为了兼容性,
// 也允许向右值赋值, 为了避免此种情况, 可以使用引用限定符
引用限定符与 参数列表后的const类似.
Foo &operator=(const Foo&) &; //此处的& 参数为const 左值
Foo &operator=(const Foo&) &&; //此处的&& 表示参数是一个右值
还可以 &和const一起使用
Foo someMethod() const &; // &只能在const 后, 参数为const 左值
重载和引用函数
重载时: 如果是const的函数, 可以有两个版本, 一个带const, 一个不带const. 如果是用引用限定的函数. 重载时 必须 都要带上引用限定符, 或者一个, 或者2个.
如下 重载 是错误的.
Foo sorted() &&;
Foo sorted() const; // 这种定义是错误的, 必须加上引用限定符.
即: 如果一个成员函数有引用限定符, 则重载的版本 都必须有引用限定符.
#include<vector>
#include<iostream>
#include<algorithm>
class Foo_1358 {
public:
Foo_1358 sorted()&&;
Foo_1358 sorted() const&;
private:
std::vector<int> data;
};
Foo_1358 Foo_1358::sorted()&& {
std::sort(data.begin(), data.end());
std::cout << "&& sort" << std::endl;
return *this;
}
Foo_1358 Foo_1358::sorted() const& {
std::cout << "const & sort" << std::endl;
// 13.56
// Foo_1358 ret(*this); // ret 是左值,
// return ret.sorted(); // 因ret是左值, 将继续调用Foo_1358::sorted() const& 从而形成死循环
// 13.57
return Foo_1358(*this).sorted(); // Foo_1358(*this) 是右值, 其将调用的sort为 Foo_1358::sorted()&& 从而不构成死循环
}