自学内容网 自学内容网

C++学习笔记----8、掌握类与对象(一)---- 对象中的动态内存分配(3)

2.3.2、Spreadsheet赋值操作符

        下面是带有赋值操作符的Spreadsheet类定义:

export class Spreadsheet
{
public:
Spreadsheet& operator=(const Spreadsheet& rhs);
    // Code omitted for brevity
};

        简单的实现可以如下:

Spreadsheet& Spreadsheet::operator=(const Spreadsheet& rhs)
{
// Check for self-assignment
if (this == &rhs) {
return *this;
}

// Free the old memory
for (size_t i{ 0 }; i < m_width; ++i) {
delete[] m_cells[i];
}
delete[] m_cells;
m_cells = nullptr;

// Allocate new memory
m_width = rhs.m_width;
m_height = rhs.m_height;

m_cells = new SpreadsheetCell * [m_width];
for (size_t i{ 0 }; i < m_width; ++i) {
m_cells[i] = new SpreadsheetCell[m_height];
}

// Copy the data
for (size_t i{ 0 }; i < m_width; ++i) {
for (size_t j{ 0 }; j < m_height; ++j) {
m_cells[i][j] = rhs.m_cells[i][j];
}
}

return *this;
}

        代码首先检查自我赋值,然后释放this对象的当前内存,分配新内存,最终拷贝每个元素。在这个函数中有许多内容,都可能会出错!this对象很可能进入一种无效状态。

        例如,假设内存成功释放,m_width与m_height进行了正确设置,但是在分配内存的循环中抛出了一个例外。当这种情况发生的时候,函数的剩余部分就不再执行,函数退出。现在Spreadsheet实例是被三十破坏的;m_width与m_height数据成员给了一定的数据,但是m_cells数据成员没有指向正确数量的内存。从根儿上说,这个代码不是例外安全的!

        我们需要的是要么都对要么什么也不要的方式;要么所有的都成功要么this对象保持没有动过。为了实现这样的一个例外安全的赋值操作符,copy-and-swap就要用到了。解释一下,给Spreadsheet类添加一个swap()成员函数。还有,推荐提供一个非成员swap()函数以便可以用于标准库算法。下面是带有赋值操作符的Spreadsheet类的定义,以及swap()成员函数和非成员函数:

export class Spreadsheet
{
public:
Spreadsheet& operator=(const Spreadsheet& rhs);
void swap(Spreadsheet& other) noexcept;
    // Code omitted for brevity
};

export void swap(Spreadsheet& first, Spreadsheet& second) noexcept;

        要求实现例外安全的copy-and-swap习语就是swap()永远不会抛出例外,所以被标注为noexcept。

        注意:被标注为noexcept的函数指出它不会抛出任何例外。noexcept必须出现在任何const关键字之后。举例如下:

void myNonThrowingConstFunction() const noexcept { /* ... */ }

        如果noexcept函数真的抛出了一个例外,程序会终止。

        swap()成员函数的实现交换每一个数据成员,使用标准库<utility>提供的std::swap()工具函数,它会高效地交换两个值:

void Spreadsheet::swap(Spreadsheet& other) noexcept
{
std::swap(m_width, other.m_width);
std::swap(m_height, other.m_height);
std::swap(m_cells, other.m_cells);
}

        非成员swap()函数简单地将swap()成员函数推到台前:

void swap(Spreadsheet& first, Spreadsheet& second) noexcept
{
first.swap(second);
}

        既然我们已经拥有了例外安全的swap(),它可以用于实现赋值操作符:

Spreadsheet& Spreadsheet::operator=(const Spreadsheet& rhs)
{
// Copy-and-swap idiom
Spreadsheet temp { rhs }; // Do all the work in a temporary instance
swap(temp); // Commit the work with only non-throwing operations
return *this;
}

        该实现使用了copy-and-swap习语。首先,对右边的进行copy,叫做temp。然后当前对象与这个拷贝进行交换。这种模式是推荐的实现赋值操作符的方式,因为它保证了强大的例外安全,意味着如果例外发生,当前Spreadsheet对象的状态保持不变。该习语应用在三个阶段:

  • 第一阶段生成一个临时的拷贝。这不会改变当前Spreadsheet对象的状态,如果在此阶段抛出例外不会有问题。
  • 第二阶段使用swap()函数交换 生成的临时拷贝与当前对象。swap()函数不会抛出例外。
  • 第三阶段析构临时对象,它现在包含了原来的对象(因为进行了交换),清理内存。

        当你不使用copy-and-swap习语来实现赋值操作符的时候,为了效率,有时也为了正确性,在赋值操作符的第一行代码通常会检查自我赋值。举例如下:

Spreadsheet& Spreadsheet::operator=(const Spreadsheet& rhs)
{
    // Check for self-­assignment
    if (this == &rhs) { return *this; }
    // ...

    return *this;
}

        如果使用copy-and-swap习语,就不需要这样的自我赋值检查。

        警告:当实现赋值操作符时,使用copy-and-swap习语来避免重复代码与保证强大的例外安全。

        注意:copy-and-swap习语不仅仅可以用于赋值操作符。它可以用于任何需要多步操作并且想要将其变成要么全部成功要么全部失败的操作:首先,拷贝;然后,在这个拷贝上进行所有的修改;最后,如果没有错误,执行一个非抛例外交换操作。

2.3.3、禁止赋值与传值

        有时候当你动态在类中分配内存时,禁止任何人拷贝与赋值给对象是最容易的。可以通过显式地删除operator=与拷贝构造函数来办到。这样的话,如果有人想要通过值来传递对象,从函数中返回,或者赋值给它,编译器都会报错。下面是一个禁止赋值与传值的Spreadsheet类的定义:

export class Spreadsheet
{
public:
    Spreadsheet(std::size_t width, std::size_t height);
    Spreadsheet(const Spreadsheet& src) = delete;
    ~Spreadsheet();

    Spreadsheet& operator=(const Spreadsheet& rhs) = delete;
    // Code omitted for brevity
};

        没有给被删除的成员函数提供实现。连接器也不会去找它们,因为编译器不允许代码调用它们。当你写代码去拷贝或者赋值给一个Spreadsheet对象时,编译器就会报错:

'Spreadsheet &Spreadsheet::operator =(const Spreadsheet &)': attempting to
reference a deleted function

原文地址:https://blog.csdn.net/weixin_71738303/article/details/142539016

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!