- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
这个成语是什么,什么时候应该使用?它解决了哪些问题?使用 C++11 时习语会改变吗?
虽然在很多地方都提到过,但我们没有任何单一的“它是什么”的问题和答案,所以就在这里。这是之前提到的地方的部分列表:
最佳答案
概述
为什么我们需要 copy-and-swap 习语?
任何管理资源的类(包装器,如智能指针)都需要实现 The Big Three 。虽然复制构造函数和析构函数的目标和实现很简单,但复制赋值运算符可以说是最微妙和最困难的。应该怎么做?需要避免哪些陷阱?
copy-and-swap 习语是解决方案,它优雅地协助赋值运算符实现两件事:避免 code duplication 和提供 strong exception guarantee 。
它是如何工作的?
Conceptually ,它通过使用复制构造函数的功能来创建数据的本地拷贝,然后使用 swap
函数获取复制的数据,将旧数据与新数据交换。然后临时拷贝销毁,同时带走旧数据。我们留下了新数据的拷贝。
为了使用 copy-and-swap 习语,我们需要三样东西:一个有效的复制构造函数、一个有效的析构函数(两者都是任何包装器的基础,所以无论如何都应该是完整的)和一个 swap
函数。
交换函数是一个非抛出函数,它交换一个类的两个对象,成员对成员。我们可能会尝试使用 std::swap
而不是提供我们自己的,但这是不可能的; std::swap
在其实现中使用了复制构造函数和复制赋值运算符,我们最终会尝试根据自身定义赋值运算符!
(不仅如此,对 swap
的不合格调用将使用我们的自定义交换运算符,跳过 std::swap
将需要的不必要的类构造和销毁。)
深入的解释
目标
让我们考虑一个具体的案例。我们想在一个其他无用的类中管理一个动态数组。我们从一个有效的构造函数、复制构造函数和析构函数开始:
#include <algorithm> // std::copy
#include <cstddef> // std::size_t
class dumb_array
{
public:
// (default) constructor
dumb_array(std::size_t size = 0)
: mSize(size),
mArray(mSize ? new int[mSize]() : nullptr)
{
}
// copy-constructor
dumb_array(const dumb_array& other)
: mSize(other.mSize),
mArray(mSize ? new int[mSize] : nullptr)
{
// note that this is non-throwing, because of the data
// types being used; more attention to detail with regards
// to exceptions must be given in a more general case, however
std::copy(other.mArray, other.mArray + mSize, mArray);
}
// destructor
~dumb_array()
{
delete [] mArray;
}
private:
std::size_t mSize;
int* mArray;
};
这个类几乎成功地管理了数组,但它需要
operator=
才能正常工作。
// the hard part
dumb_array& operator=(const dumb_array& other)
{
if (this != &other) // (1)
{
// get rid of the old data...
delete [] mArray; // (2)
mArray = nullptr; // (2) *(see footnote for rationale)
// ...and put in the new
mSize = other.mSize; // (3)
mArray = mSize ? new int[mSize] : nullptr; // (3)
std::copy(other.mArray, other.mArray + mSize, mArray); // (3)
}
return *this;
}
我们说我们完成了;这现在管理一个数组,没有泄漏。但是,它存在三个问题,在代码中按顺序标记为
(n)
。
new int[mSize]
失败,则 *this
将被修改。 (即大小不对,数据没了!) dumb_array& operator=(const dumb_array& other)
{
if (this != &other) // (1)
{
// get the new data ready before we replace the old
std::size_t newSize = other.mSize;
int* newArray = newSize ? new int[newSize]() : nullptr; // (3)
std::copy(other.mArray, other.mArray + newSize, newArray); // (3)
// replace the old data (all are non-throwing)
delete [] mArray;
mSize = newSize;
mArray = newArray;
}
return *this;
}
try
/
catch
子句,但这不是问题。
swap
函数之外,我们有所有的要求。虽然三法则成功地包含了我们的复制构造函数、赋值运算符和析构函数,但它确实应该被称为“三大半”:任何时候你的类管理一个资源,提供一个
swap
也是有意义的功能。
class dumb_array
{
public:
// ...
friend void swap(dumb_array& first, dumb_array& second) // nothrow
{
// enable ADL (not necessary in our case, but good practice)
using std::swap;
// by swapping the members of two objects,
// the two objects are effectively swapped
swap(first.mSize, second.mSize);
swap(first.mArray, second.mArray);
}
// ...
};
(
Here 是为什么
public friend swap
的解释。)现在我们不仅可以交换我们的
dumb_array
,而且交换通常可以更有效;它只是交换指针和大小,而不是分配和复制整个数组。除了功能和效率方面的这一优势外,我们现在准备实现 copy-and-swap 习语。
dumb_array& operator=(dumb_array other) // (1)
{
swap(*this, other); // (2)
return *this;
}
就是这样!一举一动,所有三个问题都得到了优雅的解决。
dumb_array& operator=(const dumb_array& other)
{
dumb_array temp(other);
swap(*this, temp);
return *this;
}
我们丢失了一个
important optimization opportunity 。不仅如此,这个选择在 C++11 中也很关键,后面会讨论。 (一般来说,一个非常有用的准则如下:如果您要在函数中复制某些内容,请让编译器在参数列表中进行复制。‡)
*this
的状态。 (我们之前手动为强大的异常保证所做的,编译器现在正在为我们做;怎么样。)
swap
是非 throw 的。我们用复制的数据交换当前数据,安全地改变我们的状态,旧数据被放入临时数据。然后在函数返回时释放旧数据。 (在参数的范围结束并调用其析构函数时。)
operator=
的单一统一实现。 (此外,我们不再对非自我分配有性能损失。)
class dumb_array
{
public:
// ...
// move constructor
dumb_array(dumb_array&& other) noexcept ††
: dumb_array() // initialize via default constructor, C++11 only
{
swap(*this, other);
}
// ...
};
这里发生了什么?回想一下移动构造的目标:从类的另一个实例中获取资源,使其处于保证可分配和可破坏的状态。
other
交换;我们知道我们类的默认构造实例可以安全地分配和销毁,因此我们知道
other
将能够在交换后执行相同的操作。
dumb_array& operator=(dumb_array other); // (1)
现在,如果
other
用右值初始化,它将是移动构造的。完美的。与 C++03 让我们通过按值获取参数来重用我们的复制构造函数功能一样,C++11 也会在适当的时候自动选择移动构造函数。 (当然,正如之前链接的文章中提到的,值的复制/移动可能会被完全省略。)
mArray
设置为 null?因为如果运算符中的任何其他代码抛出,则可能会调用
dumb_array
的析构函数;如果发生这种情况而未将其设置为 null,我们将尝试删除已删除的内存!我们通过将其设置为 null 来避免这种情况,因为删除 null 是一种无操作。
std::swap
,提供类内
swap
以及自由函数
swap
等。但这都是不必要的:任何正确使用
swap
都将通过不合格的调用我们的函数将通过
ADL 找到。一个功能就行。
noexcept
,否则即使移动有意义,某些代码(例如
std::vector
调整大小逻辑)也会使用复制构造函数。当然,只有在里面的代码没有抛出异常的情况下才标记为noexcept。
关于c++ - 什么是 copy-and-swap 习语?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3279543/
#include using namespace std; class C{ private: int value; public: C(){ value = 0;
这个问题已经有答案了: What is the difference between char a[] = ?string?; and char *p = ?string?;? (8 个回答) 已关闭
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 7 年前。 此帖子已于 8 个月
除了调试之外,是否有任何针对 c、c++ 或 c# 的测试工具,其工作原理类似于将独立函数复制粘贴到某个文本框,然后在其他文本框中输入参数? 最佳答案 也许您会考虑单元测试。我推荐你谷歌测试和谷歌模拟
我想在第二台显示器中移动一个窗口 (HWND)。问题是我尝试了很多方法,例如将分辨率加倍或输入负值,但它永远无法将窗口放在我的第二台显示器上。 关于如何在 C/C++/c# 中执行此操作的任何线索 最
我正在寻找 C/C++/C## 中不同类型 DES 的现有实现。我的运行平台是Windows XP/Vista/7。 我正在尝试编写一个 C# 程序,它将使用 DES 算法进行加密和解密。我需要一些实
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
有没有办法强制将另一个 窗口置于顶部? 不是应用程序的窗口,而是另一个已经在系统上运行的窗口。 (Windows, C/C++/C#) 最佳答案 SetWindowPos(that_window_ha
假设您可以在 C/C++ 或 Csharp 之间做出选择,并且您打算在 Windows 和 Linux 服务器上运行同一服务器的多个实例,那么构建套接字服务器应用程序的最明智选择是什么? 最佳答案 如
你们能告诉我它们之间的区别吗? 顺便问一下,有什么叫C++库或C库的吗? 最佳答案 C++ 标准库 和 C 标准库 是 C++ 和 C 标准定义的库,提供给 C++ 和 C 程序使用。那是那些词的共同
下面的测试代码,我将输出信息放在注释中。我使用的是 gcc 4.8.5 和 Centos 7.2。 #include #include class C { public:
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我的客户将使用名为 annoucement 的结构/类与客户通信。我想我会用 C++ 编写服务器。会有很多不同的类继承annoucement。我的问题是通过网络将这些类发送给客户端 我想也许我应该使用
我在 C# 中有以下函数: public Matrix ConcatDescriptors(IList> descriptors) { int cols = descriptors[0].Co
我有一个项目要编写一个函数来对某些数据执行某些操作。我可以用 C/C++ 编写代码,但我不想与雇主共享该函数的代码。相反,我只想让他有权在他自己的代码中调用该函数。是否可以?我想到了这两种方法 - 在
我使用的是编写糟糕的第 3 方 (C/C++) Api。我从托管代码(C++/CLI)中使用它。有时会出现“访问冲突错误”。这使整个应用程序崩溃。我知道我无法处理这些错误[如果指针访问非法内存位置等,
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
我有一些 C 代码,将使用 P/Invoke 从 C# 调用。我正在尝试为这个 C 函数定义一个 C# 等效项。 SomeData* DoSomething(); struct SomeData {
这个问题已经有答案了: Why are these constructs using pre and post-increment undefined behavior? (14 个回答) 已关闭 6
我是一名优秀的程序员,十分优秀!