2016-01-29 39 views
8

我有一个const正确性问题,我似乎无法解决。下面是我的程序结构:如何让这个常量纠正?

class Node 
{ 
    private: 
     int   id; 
     std::set<Node*> neighbours; 
    public: 
     Node(); 
     Node(int id_p); 

     void set_id(const int& id_p); 
     int get_id() const; 
     void add_neighbour(Node* neighbour); 
     bool is_neighbour(Node* neighbour) const; 

     friend bool operator <(const Node& lhs, const Node& rhs); 
}; 

class Graph 
{ 
    private: 
     std::set<Node> node_list; 
    public: 
     Graph(); 

     void  add_node(int id); 
     const Node* get_node_by_id(int id) const; 
     bool  has_node(int id) const; 
     void  check_add_node(int id); 
     void  add_edge(int id_1, int id_2); 
     bool  has_edge(int id_1, int id_2) const; 
     void  check_add_edge(int id_1, int id_2); 

     (...) 
}; 

现在的问题是,如果我调用函数Graph::get_node_by_id(),我想返回一个指向给定节点(类型Node)。但这似乎是不可能的,因为std::set隐式地将我的节点类型对象转换为const Node对象,而我无法从const对象获取non-const pointer对象。

但是,我不能拥有一切设置为const Node(这将解决这个问题),因为我想打电话给Node::add_neighbour()Graph::add_edge(),但每当我这样做,我的编译器说,我可能会违反const岬(必填以获得排序集)node_list集中的元素,尽管我将less operator<定义为仅关注id

有什么我可以做的,以解决这个困境(没有放弃有排序集)?谢谢您的反馈!在错误

更多信息:

如果我使用非恒定的领域,错误Graph::get_node_by_id()

for(Node& element : this->node_list) // Error: element should be const Node& 
{ 
    if(element->get_id() == id) 
    { 
     return element; 
    } 
} 
return nullptr; 

如果我使用常量领域,错误Graph::add_edge()

(...) 
const Node* node_1 = this->get_node_by_id(id_1); 
const Node* node_2 = this->get_node_by_id(id_2); 
node_1->add_neighbour(node_2); // Error for disregarding constness 
node_2->add_neighbour(node_1); 
+4

听起来像你可能想有一个'映射'映射ID节点,而不是一组。 – user2357112

+0

也许我错过了什么,但为什么不把内部设置为可变? –

回答

3

您的问题似乎是Node有两个不同的“值语义”。

一个是由operator<公开的,不受add_neighbour的影响。这是set的需求,通过制定Nodeconst来保持事物的顺序以及它的执行。

另一种是暴露在类API中,其中set_idadd_neighbour会更改该值。

要保留排序的set,您必须允许一个节点的ID在集合中进行更改。但你可以允许邻居改变。

所以我建议你做neighbourssetmutable,使add_neighbourprivateconst,使GraphNode一个friend

这就是mutable给你的数据成员,它不属于某个类型的“值”。请注意,这意味着您指示持有const Node*的东西可能会在呼叫之间发生is_neighbour的结果更改。

所以...

class Node 
{ 
    private: 
     // Trust Graph not to mess directly with these! 
     int   id; 
     mutable std::set<Node*> neighbours; 

     friend class Graph; 
     // For Graph's exclusive use 
     void add_neighbour(Node* neighbour) const; 


    public: 
     Node(); 
     Node(int id_p); 

     void set_id(const int& id_p); // Callable when not in Graph's set 
     int get_id() const; 
     void add_neighbour(Node* neighbour); // Callable when not in Graph's set 
     bool is_neighbour(Node* neighbour) const; 

     friend bool operator <(const Node& lhs, const Node& rhs); 
}; 

class Graph 
{ 
    private: 
     std::set<Node> node_list; 
    public: 
     Graph(); 

     void  add_node(int id); 
     const Node* get_node_by_id(int id) const; 
     bool  has_node(int id) const; 
     void  check_add_node(int id); 
     void  add_edge(int id_1, int id_2); 
     bool  has_edge(int id_1, int id_2) const; 
     void  check_add_edge(int id_1, int id_2); 

     (...) 
}; 

现在你有什么是公共的,非const为Node实例不在Graphset变异符,并为Graph一个额外的突变用来改变Node S的邻居在其set

因此,只有Graph可以做

const Node b; 
b.add_neighbour(nullptr); 

如果你实在不放心Graph,您可以用内class更换privateconstadd_neighbour,具有static add_neighbour(Node* node, Node* neighbour方法,因为内class隐含能访问外部类的私人数据。

class NeighbourHelper { 
    friend class Graph; 
    static void add(const Node* node, Node* neighbour) { 
     node->add_neighbour(neighbour); 
    } 

现在只能Graph可以做

const Node b; 
Node::NeighbourHelper::add(&b, nullptr); 

在这两种情况下,给大家以下工作:

Node a; 
a.add_neighbour(nullptr); 

在这一点上,你应该遭受代码 - 闻起来......问题是Graph中的publicget_node_by_id方法。您实际上可能想要公开某种类型的迭代器,而不是原始的Node*,并使Node成为Graph的私有内部类。

甚至只是更换整个Node概念,std::map<int,std::set<int>> ...

但是这取决于你的实际使用情况。

1

尽管TBBle的分析是正确的,但有一个更简单的解决方案:将Graph的std::set<Node>替换为std::map<int,Node>

您当前的Graph::get_node_by_id()正在使用线性搜索,因为set并未真正提供您想要的查找。通过外部关键字,您可以删除operator<过载,并且仍然可以更快速,更自然地查找:map.find(id)

唯一难看的部分是,现在你的Node有一个内部ID,它必须匹配一个外部密钥。如果你从不使用id,除了在地图上查找节点,你可以完全删除它。如果您需要按照图中边(邻居),然后检查ID,您可以用一组地图迭代器取代你的指针集,如:

typedef std::map<int, Node> NodeMap; 
typedef std::set<NodeMap::iterator> NeighbourMap; 

然后你遍历有可用的pair<const int,Node>


NB。经过反思,从set到map的转换产生了与TBBle的答案几乎相同的区别:将Node分解为常量和可变部分。通过这种解决方案,查找更清晰(通过将假节点构建为set::find的关键点,您可以重新获得对数时间查找,但它仍然有点不雅),而对于另一个解决方案,对象标识稍微更清晰。

+0

我想如果他只是使用'find',那么他也会得到相同的表现,因为通常'set'和'map'都是R&B树。 –

+0

是的,我在底部的说明中提到,您可以获得相同的查找复杂性 - 但必须为密钥构造临时节点有点笨拙 – Useless