C++程序  |  244行  |  8.11 KB

/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef SEMISTATIC_GRAPH_H
#define SEMISTATIC_GRAPH_H

#include "memory_pool.h"
#include <fruit/impl/data_structures/semistatic_map.h>

#if FRUIT_EXTRA_DEBUG
#include <iostream>
#endif

namespace fruit {
namespace impl {

// The alignas ensures that a SemistaticGraphInternalNodeId* always has 0 in the low-order bit.
struct alignas(2) alignas(alignof(std::size_t)) SemistaticGraphInternalNodeId {
  // This stores the index in the vector times sizeof(NodeData).
  std::size_t id;

  bool operator==(const SemistaticGraphInternalNodeId& x) const;
  bool operator<(const SemistaticGraphInternalNodeId& x) const;
};

/**
 * A direct graph implementation where most of the graph is fixed at construction time, but a few nodes and edges can be
 * added
 * later.
 *
 * Also, nodes can either be normal nodes or terminal nodes. Terminal nodes can't have outgoing edges. Note that a node
 * with no
 * outgoing edges may or may not be marked as terminal.
 *
 * While insertion of elements after construction is supported, inserting or changing the neighbors of more than O(1)
 * nodes
 * after construction will raise the cost of any further operations to more than O(1).
 *
 * Even though adding nodes/edges after construction is inefficient, it is efficient to turn non-terminal nodes into
 * terminal ones
 * (and therefore removing all the outgoing edges from the node) after construction.
 *
 * NodeId and Node must be default constructible and trivially copyable.
 */
template <typename NodeId, typename Node>
class SemistaticGraph {
private:
  using InternalNodeId = SemistaticGraphInternalNodeId;

  // The node data for nodeId is in nodes[node_index_map.at(nodeId)/sizeof(NodeData)].
  // To avoid hash table lookups, the edges in edges_storage are stored as indexes of `nodes' instead of as NodeIds.
  // node_index_map contains all known NodeIds, including ones known only due to an outgoing edge ending there from
  // another node.
  SemistaticMap<NodeId, InternalNodeId> node_index_map;

  struct NodeData {
#if FRUIT_EXTRA_DEBUG
    NodeId key;
#endif

  public:
    // If edges_begin==0, this is a terminal node.
    // If edges_begin==1, this node doesn't exist, it's just referenced by another node.
    // Otherwise, reinterpret_cast<InternalNodeId*>(edges_begin) is the beginning of the edges range.
    std::uintptr_t edges_begin;

    // An explicit "public" specifier here prevents the compiler from reordering the fields.
    // We want the edges_begin field to be stored first because that's what we're going to branch on.
  public:
    Node node;
  };

  std::size_t first_unused_index;

  FixedSizeVector<NodeData> nodes;

  // Stores vectors of edges as contiguous chunks of node IDs.
  // The NodeData elements in `nodes' contain indexes into this vector (stored as already multiplied by
  // sizeof(NodeData)).
  // The first element is unused.
  FixedSizeVector<InternalNodeId> edges_storage;

#if FRUIT_EXTRA_DEBUG
  template <typename NodeIter>
  void printGraph(NodeIter first, NodeIter last);
#endif

  NodeData* nodeAtId(InternalNodeId internalNodeId);
  const NodeData* nodeAtId(InternalNodeId internalNodeId) const;

  static NodeData* nodeAtId(NodeData* nodes_begin, InternalNodeId internalNodeId);
  static const NodeData* nodeAtId(const NodeData* nodes_begin, InternalNodeId internalNodeId);

public:
  class edge_iterator;

  class node_iterator {
  private:
    NodeData* itr;

    friend class SemistaticGraph<NodeId, Node>;

    node_iterator(NodeData* itr);

  public:
    Node& getNode();

    bool isTerminal();

    // Turns the node into a terminal node, also removing all the deps.
    void setTerminal();

    // Assumes !isTerminal().
    // neighborsEnd() is NOT provided/stored for efficiency, the client code is expected to know the number of
    // neighbors.
    edge_iterator neighborsBegin();

    bool operator==(const node_iterator&) const;
  };

  class const_node_iterator {
  private:
    const NodeData* itr;

    friend class SemistaticGraph<NodeId, Node>;

    const_node_iterator(const NodeData* itr);

  public:
    const_node_iterator(node_iterator itr);

    const Node& getNode();

    bool isTerminal();

    bool operator==(const const_node_iterator&) const;
  };


  class edge_iterator {
  private:
    // Iterator on edges_storage.
    InternalNodeId* itr;

    friend class SemistaticGraph<NodeId, Node>;
    friend class SemistaticGraph<NodeId, Node>::node_iterator;

    edge_iterator(InternalNodeId* itr);

  public:
    // getNodeIterator(graph.nodes.begin()) returns the first neighbor.
    node_iterator getNodeIterator(node_iterator nodes_begin);

    void operator++();

    // Equivalent to i times operator++ followed by getNodeIterator(nodes_begin).
    node_iterator getNodeIterator(std::size_t i, node_iterator nodes_begin);
  };

  // Constructs an *invalid* graph (as if this graph was just moved from).
  SemistaticGraph() = default;

  /**
   * A value x obtained dereferencing a NodeIter::value_type must support the following operations:
   * - x.getId(), returning a NodeId
   * - x.getValue(), returning a Node
   * - x.isTerminal(), returning a bool
   * - x.getEdgesBegin() and x.getEdgesEnd(), that if !x.isTerminal() define a range of values of type NodeId
   *   (the outgoing edges).
   *
   * This constructor is *not* defined in semistatic_graph.templates.h, but only in semistatic_graph.cc.
   * All instantiations must have a matching instantiation in semistatic_graph.cc.
   *
   * The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool.
   */
  template <typename NodeIter>
  SemistaticGraph(NodeIter first, NodeIter last, MemoryPool& memory_pool);

  SemistaticGraph(SemistaticGraph&&) = default;
  SemistaticGraph(const SemistaticGraph&) = delete;

  /**
   * Creates a copy of x with the additional nodes in [first, last). The requirements on NodeIter as the same as for the
   * 2-arg
   * constructor.
   * The nodes in [first, last) must NOT be already in x, but can be neighbors of nodes in x.
   * The new graph will share data with `x', so must be destroyed before `x' is destroyed.
   * Also, after this is called, `x' must not be modified until this object has been destroyed.
   *
   * The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool.
   */
  template <typename NodeIter>
  SemistaticGraph(const SemistaticGraph& x, NodeIter first, NodeIter last, MemoryPool& memory_pool);

  ~SemistaticGraph();

  SemistaticGraph& operator=(const SemistaticGraph&) = delete;
  SemistaticGraph& operator=(SemistaticGraph&&) = default;

  // The result is unspecified. The only guarantee is that it's the right value to pass to edge_iterator's
  // getNodeIterator() methods.
  node_iterator begin();

  node_iterator end();
  const_node_iterator end() const;

  // Precondition: `nodeId' must exist in the graph.
  // Unlike std::map::at(), this yields undefined behavior if the precondition isn't satisfied (instead of throwing).
  node_iterator at(NodeId nodeId);

  // Prefer using at() when possible, this is slightly slower.
  // Returns end() if the node ID was not found.
  node_iterator find(NodeId nodeId);
  const_node_iterator find(NodeId nodeId) const;

#if FRUIT_EXTRA_DEBUG
  // Emits a runtime error if some node was not created but there is an edge pointing to it.
  void checkFullyConstructed();
#endif
};

} // namespace impl
} // namespace fruit

#include <fruit/impl/data_structures/semistatic_graph.defn.h>

// semistatic_graph.templates.h is not included here to limit the transitive includes. Include it explicitly (in .cpp
// files).

#endif // SEMISTATIC_GRAPH_H