Coming from a Java background, and being fairly new to C++ as well as code review, I am trying to get accustomed to C++11/14 concepts. I have been reading Modern Effective C++ by Scott Meyers to internalize the concepts but I would love to hear some feedback on the implementation(s) below. I want to also add that the algorithm for finding the strongly connected components works, and I have tested it. However, before moving on I want to ensure I am writing proper C++ code.
Vertex class:
enum status:int{unvisited,visiting,visited};
template <class T>
class vertex
{
using vertex_sp = std::shared_ptr<vertex<T>>;
using vertex_wp = std::weak_ptr<vertex<T>>;
using neighbors = std::vector<vertex_wp>;
public:
static int time;
explicit vertex(T l) : label_(l), pre_(0), post_(0), status_(status::unvisited) {}
const T& get_label() const { return label_; }
const neighbors& get_neighbors() const { return neighbors_; }
bool is_unvisited() const { return status_ == unvisited; }
bool is_visiting() const { return status_ == visiting; }
bool is_visited() const { return status_ == visited; }
int get_pre() const { return pre_; }
int get_post() const { return post_; }
void add_neighbor(const vertex_sp& v) { neighbors_.emplace_back(v); }
void set_unvisited() { status_ = unvisited; }
void set_status(status s)
{
status_ = s;
if (s == visiting) pre_ = ++time;
if (s == visited) post_ = ++time;
}
friend std::ostream& operator<<(std::ostream& out, const vertex_wp& v) { return out << v.lock()->label_; }
friend std::ostream& operator<<(std::ostream& out, const vertex_sp& v) { return out << v->label_; }
template <class E> friend class graph;
template <class E> friend class edge;
private:
T label_;
neighbors neighbors_;
int pre_, post_;
status status_;
};
Edge Class:
template <class T>
class edge
{
using vertex_sp = std::shared_ptr<vertex<T>>;
public:
edge(const vertex_sp& from, const vertex_sp& to, const int cost=1):
from_(from), to_(to), cost_(cost) {}
const vertex_sp& get_from() const { return from_; }
const vertex_sp& get_to() const { return to_; }
int get_cost() const { return cost_; }
friend std::ostream& operator<<(std::ostream& out, const edge& v) { return out << "(" << v.from_ << "," << v.to_ << "," << v.cost_ << ")"; }
template <class E> friend class graph;
private:
const vertex_sp& from_;
const vertex_sp& to_;
const int cost_;
};
Graph Class:
enum class type:int{directed,undirected};
template <class T>
class graph
{
using vertex_sp = std::shared_ptr<vertex<T>>;
using vertices = std::unordered_set<vertex_sp>;
using edges = std::vector<edge<T>>;
using adj_list = std::unordered_map<vertex_sp, edges>;
public:
graph() = default;
explicit graph(const type t=type::undirected): type_(t) {}
const vertices& get_vertices() const { return vertices_; }
const edges& get_edges() const { return edges_; }
const edges& adj(const vertex_sp& v) const { return adj_.at(v); }
const adj_list& get_adj_list() { return adj_; }
void add_edge(const vertex_sp& from, const vertex_sp& to, const int cost=1)
{
from->add_neighbor(to);
vertices_.insert(from), vertices_.insert(to);
edges_.emplace_back(from, to, cost);
adj_[from].emplace_back(from, to, cost);
if (type_ == type::undirected)
{
to->add_neighbor(from);
edges_.emplace_back(to, from, cost);
adj_[from].emplace_back(to, from, cost);
}
}
std::unique_ptr<graph<T>> get_transpose()
{
for (const auto& v : vertices_) { v->neighbors_.clear(); }
auto graph_t = std::make_unique<graph<T>>(type_);
for (const auto& edge : edges_)
{
if (edge.from_ && edge.to_)
{
edge.from_->set_status(unvisited);
edge.to_->set_status(unvisited);
graph_t->add_edge(std::move(edge.to_), std::move(edge.from_), edge.cost_);
}
}
return graph_t;
}
private:
vertices vertices_;
edges edges_;
adj_list adj_;
type type_;
};
Kosaraju Implementation:
template <class T>
int vertex<T>::time = 0;
template <class T>
void time_stamps(const vertex_sp<T>& vertex_ptr, stack<vertex_sp<T>>* s)
{
auto *u = vertex_ptr.get();
u->set_status(visiting);
for (const auto& v : vertex_ptr->get_neighbors())
{
auto v_sp = v.lock();
if (v_sp->is_unvisited())
{
time_stamps(v_sp, s);
}
}
u->set_status(visited);
s->push(vertex_ptr);
}
template <class T>
void dfs(const vertex_sp<T>& vertex_ptr, vector<T>* partial)
{
auto *u = vertex_ptr.get();
u->set_status(visiting);
partial->emplace_back(u->get_label());
for (const auto& v : vertex_ptr->get_neighbors())
{
auto v_sp = v.lock();
if (v_sp->is_unvisited())
{
dfs(v_sp, partial);
}
}
u->set_status(visited);
}
template <class T>
vector<vector<T>> get_scc(const graph_up<T>& graph_ptr)
{
vector<vector<T>> sccs;
stack<vertex_sp<T>> times;
for (const auto &v : graph_ptr->get_vertices())
{
if (v->is_unvisited())
{
time_stamps(v, ×);
}
}
auto graph_t_ptr = graph_ptr->get_transpose();
while (!times.empty())
{
const auto& curr = times.top();
times.pop();
vector<T> partial;
if (curr->is_unvisited())
{
dfs(curr, &partial);
sccs.emplace_back(partial);
}
}
return sccs;
}
My main concerns are:
- I am writing C++ like Java code.
- I am using a shared pointer where a unique pointer would suffice. I thought about it for a while before using a shared pointer and opted for shared pointer because I keep several pointers to the vertices since they get copied to several containers and thus having a shared ownership.
- I am not transposing the graph efficiently, I have read that std::move has 0 overhead so I opted to use that instead of creating additional copies.
- Being new to operator overloading, are my uses above correct? Am I using the friend keyword properly?
- What tips do you all have on improving my C++ style in general, mainly on how I can improve efficiency or more importantly understand when dynamic allocation is necessary and when mere objects suffice (I want to ensure I am not abusing dynamic allocation).
Thanks in advance!