问题
I am currently trying to use the boost graph library in an existing c++ project. I would like to store objects of a custom class in a boost graph. Below is a small example with a custom class definition with two members (a string and an int) and their corresponding getter methods.
I have a few questions:
- How can I include the string and int value in the Graphviz output? I found this answer to a similar question using
boost::make_label_writer
but I not really sure if my example can be adopted to this (I am using a custom class and shared pointers). - It should not be possible to store an identical object (same string and int value) more than one time in the graph. Therefore I overloaded two comparison operators in my custom class. I also read that I have to change the second template parameter of the graph type definition to
boost::setS
but that results in a really long error message of the compiler ... Let's say I created a new object of my custom class: How can I check if it is already stored in the graph?
#include <iostream> #include <boost/graph/graphviz.hpp> class my_custom_class { public: my_custom_class(const std::string &my_string, int my_int) : my_string(my_string), my_int(my_int) {} virtual ~my_custom_class() { } std::string get_my_string() const { return my_string; } int get_int() const { return my_int; } bool operator==(const my_custom_class &rhs) const { return my_string == rhs.my_string && my_int == rhs.my_int; } bool operator!=(const my_custom_class &rhs) const { return !(rhs == *this); } private: std::string my_string; int my_int; }; namespace boost { enum vertex_my_custom_class_t { vertex_my_custom_class = 123 }; BOOST_INSTALL_PROPERTY(vertex, my_custom_class); } int main() { typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, boost::property<boost::vertex_my_custom_class_t, std::shared_ptr<my_custom_class>>> graph_t; typedef boost::graph_traits<graph_t>::vertex_descriptor vertex_t; std::shared_ptr<my_custom_class> object_one = std::make_shared<my_custom_class>("Lorem", 123); std::shared_ptr<my_custom_class> object_two = std::make_shared<my_custom_class>("ipsum", 456); std::shared_ptr<my_custom_class> object_three = std::make_shared<my_custom_class>("Lorem", 123); std::cout << "object one: " << object_one->get_int() << "; " << object_one->get_my_string() << std::endl; std::cout << "object two: " << object_two->get_int() << "; " << object_two->get_my_string() << std::endl; std::cout << "object three: " << object_three->get_int() << "; " << object_three->get_my_string() << std::endl; std::cout << std::endl; std::cout << "object one == object two: " << (*object_one == *object_two) << std::endl; std::cout << "object one == object three: " << (*object_one == *object_three) << std::endl; std::cout << std::endl; graph_t graph; vertex_t vertex_one = boost::add_vertex(object_one, graph); vertex_t vertex_two = boost::add_vertex(object_two, graph); vertex_t vertex_three = boost::add_vertex(object_three, graph); boost::add_edge(vertex_one, vertex_two, graph); boost::add_edge(vertex_one, vertex_three, graph); boost::write_graphviz(std::cout, graph); return 0; }
Program output:
object one: 123; Lorem
object two: 456; ipsum
object three: 123; Lorem
object one == object two: 0
object one == object three: 1
digraph G {
0;
1;
2;
0->1 ;
0->2 ;
}
回答1:
without changing your declarations, this is a bit painful, but possible:
{ boost::dynamic_properties dp; boost::property_map<graph_t, boost::vertex_my_custom_class_t>::type custom = get(boost::vertex_my_custom_class, graph); dp.property("node_id", boost::make_transform_value_property_map(std::mem_fn(&my_custom_class::get_int), custom)); dp.property("label", boost::make_transform_value_property_map(std::mem_fn(&my_custom_class::get_my_string), custom)); boost::write_graphviz_dp(std::cout, graph, dp); }
Printing: Live On Coliru
digraph G { 123 [label=Lorem]; 456 [label=ipsum]; 123 [label=Lorem]; 123->456 ; 123->123 ; }
You will want to take care of this externally. Why not have an intrusive set of nodes and verify the contraints that way. Changing the Vertex Container Selector as you say has no effect (it just ends up storing the vertex descriptors in ascending order, while they stay unique as they were before).
A side effect is changing from contiguously allocated vertex storage to node-based (pro: iterator/reference stability, con: allocation overhead, reduced locality of reference, non-implicit vertex_index). The latter is the culprit: many things in BGL require a vertex index and if one is not implied (by using
vecS
e.g.) you have to pass one.Interestingly, since I used
write_graphviz_dp
with a specificnode_id
property there's no need for the implicit vertex index just now, so you could changevecS
tosetS
and observe the behaviour: Live On ColiruI think that's not the right time to check. There is not really a better way than to visit all vertices, unless you keep an external index updated.
Style/Simplification
Since std::shared_ptr
implies you have c++11, let's use it.
Also, the whole dance with a custom property is mostly a way more clumsy of doing property bundles: these are syntactically easier and better supported.
See the difference:
Note I was over-enthusiastic for a second, using a shared-ptr you still require the the transform-value-property-map:
Live On Coliru
#include <boost/graph/graphviz.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <iostream>
struct MyVertex {
MyVertex(std::string label, int id) : _label(std::move(label)), _id(id) {}
std::string label() const { return _label; }
int id() const { return _id; }
bool operator<(const MyVertex &rhs) const { return std::tie(_id, _label) < std::tie(rhs._id, rhs._label); }
private:
std::string _label;
int _id;
};
using graph_t = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, std::shared_ptr<MyVertex>>;
int main() {
graph_t graph;
auto v1 = add_vertex(std::make_shared<MyVertex>("Lorem", 123), graph);
auto v2 = add_vertex(std::make_shared<MyVertex>("ipsum", 456), graph);
auto v3 = add_vertex(std::make_shared<MyVertex>("Lorem", 123), graph);
add_edge(v1, v2, graph);
add_edge(v1, v3, graph);
{
boost::dynamic_properties dp;
auto bundle = get(boost::vertex_bundle, graph);
dp.property("node_id", make_transform_value_property_map(std::mem_fn(&MyVertex::id), bundle));
dp.property("label", make_transform_value_property_map(std::mem_fn(&MyVertex::label), bundle));
write_graphviz_dp(std::cout, graph, dp);
}
}
Comparing: Without Shared Pointers
I'm not saying you must not use it, but I'm also not convinced you need it. So, let's remove it so you see the difference:
Live On Coliru
#include <boost/graph/graphviz.hpp>
#include <iostream>
struct MyVertex {
std::string label;
int id;
bool operator<(const MyVertex &rhs) const { return std::tie(id, label) < std::tie(rhs.id, rhs.label); }
};
using graph_t = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, MyVertex>;
int main() {
graph_t graph;
auto v1 = add_vertex({"Lorem", 123}, graph);
auto v2 = add_vertex({"ipsum", 456}, graph);
auto v3 = add_vertex({"Lorem", 123}, graph);
add_edge(v1, v2, graph);
add_edge(v1, v3, graph);
boost::dynamic_properties dp;
dp.property("node_id", boost::get(&MyVertex::id, graph));
dp.property("label", boost::get(&MyVertex::label, graph));
write_graphviz_dp(std::cout, graph, dp);
}
That's roughly half the code. From here we can explore how to add the desired feature(s)
Uniqueness: Let's try Our Hand
The simplest thing to do is to check the existing nodes before adding a new one:
Live On Coliru
#include <boost/graph/graphviz.hpp>
#include <boost/range/iterator_range.hpp>
#include <iostream>
struct MyVertex {
std::string label;
int id;
auto key() const { return std::tie(id,label); }
bool operator< (const MyVertex &rhs) const { return key() < rhs.key(); }
bool operator==(const MyVertex &rhs) const { return key() == rhs.key(); }
bool operator!=(const MyVertex &rhs) const { return key() != rhs.key(); }
};
using graph_t = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, MyVertex>;
int main() {
graph_t graph;
auto node = [&graph](std::string name, int id) {
for (auto&& v : boost::make_iterator_range(vertices(graph)))
if (graph[v] == MyVertex{name, id})
return v;
return add_vertex({name, id}, graph);
};
auto v1 = node("Lorem", 123);
auto v2 = node("ipsum", 456);
auto v3 = node("Lorem", 123);
assert(v3==v1);
add_edge(v1, v2, graph);
add_edge(v1, v3, graph);
boost::dynamic_properties dp;
dp.property("node_id", boost::get(&MyVertex::id, graph));
dp.property("label", boost::get(&MyVertex::label, graph));
write_graphviz_dp(std::cout, graph, dp);
}
Note how v3
now equals v1
:
digraph G {
123 [label=Lorem];
456 [label=ipsum];
123->456 ;
123->123 ;
}
来源:https://stackoverflow.com/questions/46857868/using-boost-graph-library-with-a-custom-class-for-vertices