具有强大异常保证的同时 STL 容器插入
Simultaneous STL container insertion with strong exception guarantee
我想同时插入多个STL容器,要么全部成功,要么全部不变。我怎样才能优雅地做到这一点?
在我的示例中,我有一个自定义容器,其中包含各种 STL 容器作为数据成员。插入操作插入到各种容器中,但如果其中一个容器失败,则需要撤消更改以确保所有不变量仍然有效。我怎样才能(很好地)做到这一点?
我的第一直觉是回溯所有已插入的内容并撤消它们。这似乎过分了,但我希望我的例子能说明这一点。
示例(抱歉,如果不是“足够小”):
struct DataItem {
int x;
float y;
char c;
};
class MyContainer {
public:
void Insert(std::vector<std::string> names,
std::unordered_set<int> categories,
DataItem data);
private:
/* invariant: if a string 'name' is mapped to an id 'i' in 'name_to_id_'
it must be the position of a 'DataItem' object in 'data_'
and contained in at least one of the categories */
std::map<std::string, int> name_to_id_;
std::map<int, std::unordered_set<int>> categories_;
std::vector<DataItem> data_;
};
MyContainer::Insert(std::vector<std::string> names,
std::unordered_set<Categories> categories,
DataItem data) {
/* ensure names, and categories are not empty and data is valid */
int id = data_.size();
for (auto it = names.begin(); it != names.end(); ++it) {
if (this->name_to_id_.count(name)) {
/* Clean up all names inserted so far by iterating backwards to names.begin()
using this->name_to_id_.erase */
throw SomeException("Insertion failed");
}
try {
this->name_to_id_.insert({*it, id});
} catch (/* what do I catch here? */) {
/* Clean up all names inserted so far by iterating backwards to names.begin()
using this->name_to_id_.erase */
throw SomeException("Insertion failed");
}
}
for (auto it = categories.begin(); it != categories.end(); ++it) {
try {
this->categories_.at(*it).insert(id);
} catch (/* what do I catch here? */) {
/* Clean up all names and all categories id was inserted into so far by
iterating backwards to categories.begin() using
this->categories_.at(*it).erase(id) */
throw SomeException("Insertion failed");
}
}
try {
this->data_.push_back(data);
} catch (/* what do I catch here? */) {
/* Once again clean up all categories and names... */
throw SomeException("Insertion failed");
}
}
即使不写出清理,这也似乎过分了。特别是考虑到 insert
和 push_back
应该很少失败。这真的是我需要做的吗?
此外,我可以确定对 std::unordered_set
和 std::map
所做的更改的最安全方法是 find
和 erase
的组合,但我找不到任何关于 find
的异常安全性的信息。它总是成功吗?
我想到 insert
和 push_back
语句必须包含在 try
块中才能真正处理异常,但我捕获了什么异常?
如果你可以修改DataItem
的构造函数,你可以让它从上一个项目的构造函数中插入东西到下一个容器中。这是一个工作演示
#include <vector>
#include <list>
#include <functional>
#include <iostream>
struct mydata
{
int a;
double b;
template <typename F>
mydata (int a, double b, F func) : a(a), b(b) { func(); }
};
int main()
{
std::vector<mydata> one;
std::vector<mydata> two;
std::list<mydata> three;
std::list<mydata> four;
try {
one.emplace_back (1, 2, [&]() {
two.emplace_back (1, 2, [&]() {
three.emplace_back(1, 2, [&]() {
four.emplace_back (1, 2, [ ]() {
throw 42; // simulate failure at the last one
}); }); }); });
} catch (int x) {
// handle the failure
}
std::cout << one.size() << " "
<< two.size() << " "
<< three.size() << " "
<< four.size() << "\n";
}
程序按预期打印四个零。
我没有说明实际的清理操作,也没有说明最有效的方法是什么。您需要记录插入在哪个阶段失败并进行相应处理。
struct DataItem {
int x;
float y;
char c;
};
class SomeException : std::exception
{
public:
SomeException(char *c) : std::exception(c) {};
};
class MyContainer {
public:
void Insert(std::vector<std::string> names,
std::unordered_set<int> categories,
DataItem data);
private:
void CleanUp();
private:
/* invariant: if a string 'name' is mapped to an id 'i' in 'name_to_id_'
it must be the position of a 'DataItem' object in 'data_'
and contained in at least one of the categories */
std::map<std::string, int> name_to_id_;
std::map<int, std::unordered_set<int>> categories_;
std::vector<DataItem> data_;
};
void MyContainer::CleanUp()
{
// Do your clean up here
}
void MyContainer::Insert(std::vector<std::string> names,
std::unordered_set<int> categories,
DataItem data)
{
bool failed = false;
/* ensure names, and categories are not empty and data is valid */
int id = data_.size();
for (auto it = names.begin(); it != names.end() && !failed; ++it) {
if (this->name_to_id_.count(*it))
failed = true;
try {
this->name_to_id_.insert({ *it, id });
}
catch (...) {
failed = true;
}
}
for (auto it = categories.begin(); it != categories.end() && !failed; ++it) {
try {
this->categories_.at(*it).insert(id);
}
catch (...) {
failed = true;
}
}
try {
if (!failed)
this->data_.push_back(data);
}
catch (...) {
failed = true;
}
if (failed)
CleanUp();
}
我想同时插入多个STL容器,要么全部成功,要么全部不变。我怎样才能优雅地做到这一点?
在我的示例中,我有一个自定义容器,其中包含各种 STL 容器作为数据成员。插入操作插入到各种容器中,但如果其中一个容器失败,则需要撤消更改以确保所有不变量仍然有效。我怎样才能(很好地)做到这一点?
我的第一直觉是回溯所有已插入的内容并撤消它们。这似乎过分了,但我希望我的例子能说明这一点。
示例(抱歉,如果不是“足够小”):
struct DataItem {
int x;
float y;
char c;
};
class MyContainer {
public:
void Insert(std::vector<std::string> names,
std::unordered_set<int> categories,
DataItem data);
private:
/* invariant: if a string 'name' is mapped to an id 'i' in 'name_to_id_'
it must be the position of a 'DataItem' object in 'data_'
and contained in at least one of the categories */
std::map<std::string, int> name_to_id_;
std::map<int, std::unordered_set<int>> categories_;
std::vector<DataItem> data_;
};
MyContainer::Insert(std::vector<std::string> names,
std::unordered_set<Categories> categories,
DataItem data) {
/* ensure names, and categories are not empty and data is valid */
int id = data_.size();
for (auto it = names.begin(); it != names.end(); ++it) {
if (this->name_to_id_.count(name)) {
/* Clean up all names inserted so far by iterating backwards to names.begin()
using this->name_to_id_.erase */
throw SomeException("Insertion failed");
}
try {
this->name_to_id_.insert({*it, id});
} catch (/* what do I catch here? */) {
/* Clean up all names inserted so far by iterating backwards to names.begin()
using this->name_to_id_.erase */
throw SomeException("Insertion failed");
}
}
for (auto it = categories.begin(); it != categories.end(); ++it) {
try {
this->categories_.at(*it).insert(id);
} catch (/* what do I catch here? */) {
/* Clean up all names and all categories id was inserted into so far by
iterating backwards to categories.begin() using
this->categories_.at(*it).erase(id) */
throw SomeException("Insertion failed");
}
}
try {
this->data_.push_back(data);
} catch (/* what do I catch here? */) {
/* Once again clean up all categories and names... */
throw SomeException("Insertion failed");
}
}
即使不写出清理,这也似乎过分了。特别是考虑到 insert
和 push_back
应该很少失败。这真的是我需要做的吗?
此外,我可以确定对 std::unordered_set
和 std::map
所做的更改的最安全方法是 find
和 erase
的组合,但我找不到任何关于 find
的异常安全性的信息。它总是成功吗?
我想到 insert
和 push_back
语句必须包含在 try
块中才能真正处理异常,但我捕获了什么异常?
如果你可以修改DataItem
的构造函数,你可以让它从上一个项目的构造函数中插入东西到下一个容器中。这是一个工作演示
#include <vector>
#include <list>
#include <functional>
#include <iostream>
struct mydata
{
int a;
double b;
template <typename F>
mydata (int a, double b, F func) : a(a), b(b) { func(); }
};
int main()
{
std::vector<mydata> one;
std::vector<mydata> two;
std::list<mydata> three;
std::list<mydata> four;
try {
one.emplace_back (1, 2, [&]() {
two.emplace_back (1, 2, [&]() {
three.emplace_back(1, 2, [&]() {
four.emplace_back (1, 2, [ ]() {
throw 42; // simulate failure at the last one
}); }); }); });
} catch (int x) {
// handle the failure
}
std::cout << one.size() << " "
<< two.size() << " "
<< three.size() << " "
<< four.size() << "\n";
}
程序按预期打印四个零。
我没有说明实际的清理操作,也没有说明最有效的方法是什么。您需要记录插入在哪个阶段失败并进行相应处理。
struct DataItem {
int x;
float y;
char c;
};
class SomeException : std::exception
{
public:
SomeException(char *c) : std::exception(c) {};
};
class MyContainer {
public:
void Insert(std::vector<std::string> names,
std::unordered_set<int> categories,
DataItem data);
private:
void CleanUp();
private:
/* invariant: if a string 'name' is mapped to an id 'i' in 'name_to_id_'
it must be the position of a 'DataItem' object in 'data_'
and contained in at least one of the categories */
std::map<std::string, int> name_to_id_;
std::map<int, std::unordered_set<int>> categories_;
std::vector<DataItem> data_;
};
void MyContainer::CleanUp()
{
// Do your clean up here
}
void MyContainer::Insert(std::vector<std::string> names,
std::unordered_set<int> categories,
DataItem data)
{
bool failed = false;
/* ensure names, and categories are not empty and data is valid */
int id = data_.size();
for (auto it = names.begin(); it != names.end() && !failed; ++it) {
if (this->name_to_id_.count(*it))
failed = true;
try {
this->name_to_id_.insert({ *it, id });
}
catch (...) {
failed = true;
}
}
for (auto it = categories.begin(); it != categories.end() && !failed; ++it) {
try {
this->categories_.at(*it).insert(id);
}
catch (...) {
failed = true;
}
}
try {
if (!failed)
this->data_.push_back(data);
}
catch (...) {
failed = true;
}
if (failed)
CleanUp();
}