没有办法判断一个顶点描述符是否有效。
你只能检查
- 范围(在积分顶点描述符的情况下,如 vecS)
- 通过查找基于节点的顶点存储描述符。
但是,这两种情况都有可能给您带来错误的结果,原因与标准库容器相同:
std::vector<int> is{1,2,3};
auto i1 = is.begin();
is.push_back(4);
std::cout << "Undefined Behaviour: " << *i1;
关键是,迭代器(或描述符,视情况而定)已失效。无法检测到这种情况发生了¹,您将始终必须自己处理。
迭代器/描述符失效保证遵循底层容器的保证。这意味着对于基于节点的容器,您实际上可以依赖描述符(和引用)在插入甚至删除过程中保持稳定(显然,除了对于被删除的元素)。
参见例如Iterator invalidation rules
所以对于整数描述符,你会写:
bool descriptor_looks_valid(vertex_descriptor v) const {
return v>=0 && v < num_vertices(g);
}
如您所知,对于大多数其他容器选择器来说,它的效率会非常低:
bool descriptor_looks_valid(vertex_descriptor v) const {
auto vds = vertices(g);
return std::count(vds.first, vds.second, v);
}
或者一般来说(假设 c++17):
bool descriptor_looks_valid(vertex_descriptor v) const {
if constexpr(std::is_integral_v<vertex_descriptor>) {
return v>=0 && v < num_vertices(g);
} else {
auto vds = vertices(g);
return std::count(vds.first, vds.second, v);
}
}
危险示范
这个小演示展示了将“范围检查通过”误认为“有效”的危险。这个程序重复这个:
template <typename vertexS> void doTest() {
using Graph = boost::adjacency_list<
boost::vecS,
vertexS,
boost::directedS,
PropertyObj>;
Graph g;
auto v1 = add_vertex({"one"}, g);
auto v2 = add_vertex({"two"}, g);
auto v3 = add_vertex({"three"}, g);
auto v4 = add_vertex({"four"}, g);
auto v5 = add_vertex({"five"}, g);
Wrapper w = g;
std::cout << w.get_property_obj(v3).something << std::endl;
// but this is confounding, and only accidentally "works" for vecS:
remove_vertex(v1, g);
std::cout << w.get_property_obj(v3).something << std::endl;
try {
// this looks "valid" with vecS, but should throw for listS
//
// of course, like with v3 the descriptor was already invalidated both cases
std::cout << w.get_property_obj(v1).something << std::endl;
} catch(std::range_error const& re) {
std::cout << "(range_error cautgh: " << re.what() << "\n";
}
}
对于vertexS 等于vecS、listS 或setS。典型输出为 Live On Coliru:
Testing with vecS:
three
four
two
Testing with listS:
three
three
(range_error caught: get_property_obj
Testing with setS:
three
three
(range_error caught: get_property_obj
结论
没有实现有效性检查的原因是底层容器不支持它们。
此外,尽管您可以“近似”验证,但这只会防止崩溃,而不是防止未指定的行为。
事实上,根据预期的语义,您可能会触发相同的Undefined Behavior(例如,如果您假设get_property_obj(v3) 每次都产生相同的值,那么您将使用vecS 破坏代码)。
我能否以某种方式在包装类中简单检查 v 是否有效?
简而言之,不。描述符有效性是使用模式的函数,调用者必须考虑到它。
完整列表
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <iostream>
struct PropertyObj {
std::string something;
};
template <class Graph, class T = PropertyObj>
class Wrapper {
public:
using vertex_descriptor = typename Graph::vertex_descriptor;
T& get_property_obj(vertex_descriptor v) {
if (!descriptor_looks_valid(v))
throw std::range_error("get_property_obj");
return g[v];
}
Wrapper(Graph& g) : g(g){}
private:
bool descriptor_looks_valid(vertex_descriptor v) const {
if constexpr(std::is_integral_v<vertex_descriptor>) {
return v>=0 && v < num_vertices(g);
} else {
auto vds = vertices(g);
return std::count(vds.first, vds.second, v);
}
}
Graph& g;
};
template <typename vertexS> void doTest() {
using Graph = boost::adjacency_list<
boost::vecS,
vertexS,
boost::directedS,
PropertyObj>;
Graph g;
auto v1 = add_vertex({"one"}, g);
auto v2 = add_vertex({"two"}, g);
auto v3 = add_vertex({"three"}, g);
auto v4 = add_vertex({"four"}, g);
auto v5 = add_vertex({"five"}, g);
boost::ignore_unused_variable_warning(v1);
boost::ignore_unused_variable_warning(v2);
boost::ignore_unused_variable_warning(v3);
boost::ignore_unused_variable_warning(v4);
boost::ignore_unused_variable_warning(v5);
Wrapper w = g;
std::cout << w.get_property_obj(v3).something << std::endl;
// but this is confounding, and only accidentally "works" for vecS:
remove_vertex(v1, g);
std::cout << w.get_property_obj(v3).something << std::endl;
try {
// this looks "valid" with vecS, but should throw for listS
//
// of course, like with v3 the descriptor was already invalidated both cases
std::cout << w.get_property_obj(v1).something << std::endl;
} catch(std::range_error const& re) {
std::cout << "(range_error caught: " << re.what() << "\n";
}
}
int main() {
std::cout << "Testing with vecS:\n";
doTest<boost::vecS>();
std::cout << "\nTesting with listS:\n";
doTest<boost::listS>();
std::cout << "\nTesting with setS:\n";
doTest<boost::setS>();
}
¹尽管某些库实现具有允许您在某些时候检测到它的扩展 - 例如 https://docs.microsoft.com/en-us/cpp/standard-library/debug-iterator-support?view=vs-2019