文章目錄
- 概述
- 13.1拷貝、賦值與銷毀
- 合成拷貝構造函數
- 拷貝初始化
- 參數和返回值
- 拷貝初始化的限制
- 編譯器可以繞過拷貝構造函數
- 拷貝運算符
- 析構函數
- 三/五原則
- 使用=default
- 阻止拷貝
- 合成的拷貝控制成員可能是刪除的
- private拷貝控制
- 拷貝控制和資源管理
- 行為像值的類
- 類值拷貝賦值運算符
- 定義行為像指針的類
- 引用計數
- 定義一個引用計數的類
- 類指針的拷貝成員“篡改” 引用計數
- 交換操作
- 拷貝控制示例
- 動態內存管理
- 對象移動
- 右值引用
- 移動構造函數和移動賦值運算符
- 移動迭代器
概述
13.1拷貝、賦值與銷毀
合成拷貝構造函數
拷貝初始化
參數和返回值
拷貝初始化的限制
編譯器可以繞過拷貝構造函數
拷貝運算符
析構函數
三/五原則
使用=default
阻止拷貝
### 析構函數不能是刪除成員
合成的拷貝控制成員可能是刪除的
private拷貝控制
在新標準發布之前 ,類是通過將其拷貝構造函數和拷貝賦值運算符蘆明為 pr ivate
的來阻止拷貝:
class Priva teCopy {
//元訪問說明符 ;接下來的成員默認為p rivate 的;參見 7.2 節 ( 第 240 頁 )
//拷貝控制成員是 priv a te 的,因此普通用戶代碼無法訪問
Priva teCopy ( const Priva teCopy& ) ;
PrivateCopy & operator= ( const Priva teCopy& ) ;
//其他成員
public :
Pr ivateCopy ( ) = def au lt ; //使用合成的默認構造函數
~PrivateCopy ( ) ; // 用尸可以定義此類型的對象 ,但無法拷貝它們
由于析構函數是 public 的,用戶可以定義 Pr ivateCopy 類型的對象 。但是 ,由于拷貝 構造函數和拷貝賦值運算符是 pr iva te 的,用戶代碼將不能拷貝這個類型的對象
。但是, 友元和成員函數仍舊可以拷貝對象 。為了阻止友元和成員函數進行拷貝,我們將這些拷貝 控制成員聲明為 pr iva t e 的,但并不定義它們 。
聲明但不定義 個成員函數是合法的 ( 參見 6. 1.2 節,第 186 頁),對此只有一個例外 , 我們將在 15.2.1 節 (第 528 頁) 中介紹。試圖訪問一個未定義的成員將導致
一個鏈接時錯 誤。通過聲明 (但不定義) pr ivate 的拷貝構造函數 ,我們可以預先阻止任何拷貝該類 型對象的企圖 :試圖拷貝對象的用戶代碼將在編譯階段被標記為錯 誤:成員函數或友元函
數中的拷貝操作將會導致鏈接時錯誤 。
拷貝控制和資源管理
行為像值的類
類值拷貝賦值運算符
定義行為像指針的類
引用計數
定義一個引用計數的類
類指針的拷貝成員“篡改” 引用計數
交換操作
拷貝控制示例
動態內存管理
對象移動
右值引用
移動構造函數和移動賦值運算符
#pragma once
#include <iostream>
class HasPtr
{friend void swap(HasPtr&, HasPtr&);
public://默認構造函數HasPtr(const std::string& s = std::string()) : ps(new std::string(s)), i(0){}//拷貝構造函數HasPtr(const HasPtr& data) : ps(new std::string(*(data.ps))), i(data.i){}//添加都移動構造函數HasPtr(HasPtr&& P)noexcept :ps(P.ps), i(P.i) {P.ps = nullptr;P.i = 0;}HasPtr& operator=(HasPtr rhs){swap(*this, rhs);return *this;}// 析構函數~HasPtr(){delete ps;}
private:std::string* ps;int i;
};inline void swap(HasPtr& lhs, HasPtr& rhs)
{using std::swap;swap(lhs.ps, rhs.ps); // swap the pointers, not the string dataswap(lhs.i, rhs.i); // swap the int members
}
int main()
{HasPtr data1, data2("123"), data3, data4("abc");data1 = data2;//拷貝語義data3 = std::move(data4);//移動語義
}
所有五個拷貝控制成員應該看作一個整體:一般來說,如果一個類定義了任何一個拷貝操作,它應該定義所有五個操作。如前所述,某些類必須定義拷貝構造函數、拷貝賦值運算符和析構函數。這些類只有一個資源,而拷貝成員必須拷貝此資源。
移動迭代器
#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <algorithm>
#include <cassert>class StrVec {
public:// 默認構造函數StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) {std::cout << "Default constructor called." << std::endl;}// 拷貝構造函數StrVec(const StrVec& s) {std::cout << "Copy constructor called." << std::endl;auto newdata = alloc_n_copy(s.begin(), s.end());elements = newdata.first;first_free = cap = newdata.second;}// 移動構造函數StrVec(StrVec&& s) noexcept : elements(s.elements), first_free(s.first_free), cap(s.cap) {std::cout << "Move constructor called." << std::endl;s.elements = s.first_free = s.cap = nullptr;}// 拷貝賦值運算符StrVec& operator=(const StrVec& rhs) {std::cout << "Copy assignment operator called." << std::endl;auto data = alloc_n_copy(rhs.begin(), rhs.end());free();elements = data.first;first_free = cap = data.second;return *this;}// 移動賦值運算符StrVec& operator=(StrVec&& rhs) noexcept {std::cout << "Move assignment operator called." << std::endl;if (this != &rhs) {free();elements = rhs.elements;first_free = rhs.first_free;cap = rhs.cap;rhs.elements = rhs.first_free = rhs.cap = nullptr;}return *this;}// 析構函數~StrVec() {std::cout << "Destructor called." << std::endl;free();}// 拷貝版本的 push_backvoid push_back(const std::string& s) {std::cout << "Copy push_back called with: " << s << std::endl;chk_n_alloc();alloc.construct(first_free++, s);}// 移動版本的 push_backvoid push_back(std::string&& s) {std::cout << "Move push_back called with: " << s << std::endl;chk_n_alloc();alloc.construct(first_free++, std::move(s));}// 返回元素數量size_t size() const { return first_free - elements; }// 返回容量size_t capacity() const { return cap - elements; }// 返回起始迭代器std::string* begin() const { return elements; }// 返回結束迭代器std::string* end() const { return first_free; }// 左值版本的 sorted 方法void sorted() & {std::cout << "Lvalue sorted called." << std::endl;std::sort(begin(), end());}// 右值版本的 sorted 方法StrVec sorted() && {std::cout << "Rvalue sorted called." << std::endl;std::sort(begin(), end());return std::move(*this);}private:std::allocator<std::string> alloc;// 檢查是否需要重新分配內存void chk_n_alloc() {if (size() == capacity()) {reallocate();}}// 分配并復制元素std::pair<std::string*, std::string*> alloc_n_copy(const std::string*, const std::string*);// 釋放內存void free();// 重新分配內存void reallocate();std::string* elements;std::string* first_free;std::string* cap;
};// 分配并復制元素的實現
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, std::uninitialized_copy(b, e, data)};
}// 釋放內存的實現
void StrVec::free() {if (elements) {for (auto p = first_free; p != elements; ) {alloc.destroy(--p);}alloc.deallocate(elements, cap - elements);}
}// 重新分配內存的實現
void StrVec::reallocate() {std::cout << "Reallocating memory..." << std::endl;auto newcapacity = size() ? 2 * size() : 1;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;
}// 打印 StrVec 中的元素
void printStrVec(const StrVec& vec) {for (auto it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;
}// 測試默認構造函數
void testDefaultConstructor() {StrVec vec;assert(vec.size() == 0);std::cout << "Default constructor test passed." << std::endl;
}// 測試拷貝構造函數
void testCopyConstructor() {StrVec vec1;vec1.push_back("apple");vec1.push_back("banana");StrVec vec2(vec1);assert(vec2.size() == vec1.size());auto it1 = vec1.begin();auto it2 = vec2.begin();while (it1 != vec1.end() && it2 != vec2.end()) {assert(*it1 == *it2);++it1;++it2;}std::cout << "Copy constructor test passed." << std::endl;
}// 測試移動構造函數
void testMoveConstructor() {StrVec vec1;vec1.push_back("cherry");vec1.push_back("date");StrVec vec2(std::move(vec1));assert(vec1.size() == 0);assert(vec2.size() == 2);std::cout << "Move constructor test passed." << std::endl;
}// 測試拷貝賦值運算符
void testCopyAssignmentOperator() {StrVec vec1;vec1.push_back("elderberry");vec1.push_back("fig");StrVec vec2;vec2 = vec1;assert(vec2.size() == vec1.size());auto it1 = vec1.begin();auto it2 = vec2.begin();while (it1 != vec1.end() && it2 != vec2.end()) {assert(*it1 == *it2);++it1;++it2;}std::cout << "Copy assignment operator test passed." << std::endl;
}// 測試移動賦值運算符
void testMoveAssignmentOperator() {StrVec vec1;vec1.push_back("grape");vec1.push_back("honeydew");StrVec vec2;vec2 = std::move(vec1);assert(vec1.size() == 0);assert(vec2.size() == 2);std::cout << "Move assignment operator test passed." << std::endl;
}// 測試 push_back 方法
void testPushBack() {StrVec vec;vec.push_back("apple");vec.push_back(std::string("banana"));assert(vec.size() == 2);std::cout << "Push_back test passed." << std::endl;
}// 測試 sorted 方法(左值)
void testSortedLvalue() {StrVec vec;vec.push_back("banana");vec.push_back("apple");vec.push_back("cherry");vec.sorted();auto it = vec.begin();assert(*it == "apple");++it;assert(*it == "banana");++it;assert(*it == "cherry");std::cout << "Sorted (lvalue) test passed." << std::endl;
}// 測試 sorted 方法(右值)
void testSortedRvalue() {StrVec sortedVec = StrVec().push_back("grape").push_back("date").push_back("elderberry").sorted();auto it = sortedVec.begin();assert(*it == "date");++it;assert(*it == "elderberry");++it;assert(*it == "grape");std::cout << "Sorted (rvalue) test passed." << std::endl;
}int main() {testDefaultConstructor();testCopyConstructor();testMoveConstructor();testCopyAssignmentOperator();testMoveAssignmentOperator();testPushBack();testSortedLvalue();testSortedRvalue();std::cout << "All tests passed!" << std::endl;return 0;
}
}