我特别是在谈论函数std::vector::emplace()
,据我所知,该函数必须提供强大的异常保证。也就是说,此函数在内部抛出异常的情况下一定不能有任何副作用。
我一直在这里寻找有关标准容器中异常安全性的信息,并且我可以看到一些对以下C ++标准摘录的引用。
23.2.1 / 10:
除非另有规定(请参见23.2.4.1、23.2.5.1、23.3.3.4和23.3.6.5),否则本条款中定义的所有容器类型均满足以下附加要求:
-如果在插入单个元素时insert()或emplace()函数引发异常,则该函数无效。
因此,是的,无论发生什么情况,标准容器以及其中的std::vector
都不得因insert()
/ emplace()
操作而改变。现在,观察以下课程。
int ObjectID;
class Foo
{
public:
Foo()
: ID(++ObjectID) // just some ID to distinguish different objects
{
std::cout << "default ctor" << std::endl;
}
Foo(const Foo& other)
: ID(other.ID)
{
std::cout << "copy ctor" << std::endl;
}
Foo& operator=(const Foo& other)
{
ID = other.ID;
std::cout << "copy assignment operator" << std::endl;
return *this;
}
Foo(Foo&& other)
: ID(other.ID)
{
std::cout << "move ctor" << std::endl;
}
Foo& operator=(Foo&& other)
{
ID = other.ID;
std::cout << "move assignment operator" << std::endl;
throw std::exception(); // yes, move assignment operator does throw!
return *this;
}
int GetNumber() const { return ID; }
private:
int ID;
};
现在让我们尝试使std::vector
违反标准。
void PrintV(const std::vector<Foo>& v)
{
std::cout << "v.size() = " << v.size() << std::endl;
for (size_t i = 0; i < v.size(); ++i)
std::cout << "v[" << i << "] = " << v[i].GetNumber() << std::endl;
}
int main()
{
std::vector<Foo> v;
v.reserve(4);
v.emplace_back();
v.emplace_back();
v.emplace_back();
// let's have a look at the vector, it must be fine now
PrintV(v);
try
{
v.emplace(v.begin());
}
catch (const std::exception& ex)
{
std::cout << "an exception has been caught!" << std::endl;
}
// look! the vector has changed after an exception
// was thrown though it shouldn't have
PrintV(v);
}
简而言之,这段代码首先为一个向量中的4个元素分配了足够的容量,然后在其中添加了3个元素(通过将它们换回来),然后在开始处插入了一个新元素,这导致将所有其他元素转移到对。这就是我在输出中得到的。
default ctor
default ctor
default ctor
v.size() = 3
v[0] = 1
v[1] = 2
v[2] = 3
move ctor
move assignment operato
an exception has been caught!
v.size() = 4
v[0] = 1
v[1] = 2
v[2] = 2
v[3] = 3
一切都很好,直到最后一个emplace()
发生为止。调用之后,我们可以看到std::vector
的实现是如何首先从第三个元素移动构造第四个元素,然后在引发异常时开始将第二个元素移动分配给第三个元素。如您所见,向量结果发生了变化,因此功能emplace()
具有副作用,尽管不需要这样做。如果我使用insert()
而不是emplace()
,也会发生同样的情况。
这种违反标准的原因是什么?在这种情况下实现强大的异常安全性是否太昂贵?还是我在这里理解错了?