如何纠正这个常量?
How to have this const-corrected?
我有一个似乎无法解决的常量正确性问题。这是我的程序的结构:
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()
,我想 return 一个指向给定节点(类型 Node
)的指针。但这样做似乎是不可能的,因为 std::set
隐式地将我的 Node 类型对象转换为 const Node
对象,而我无法从 const
对象中获取 non-const pointer
。
但是,我不能将其他所有设置都设置为 const Node
(这会解决问题),因为我想从 Graph::add_edge()
调用 Node::add_neighbour()
,但每当我这样做时,我的编译器说我可能违反了 node_list
集合中元素的 const
ness(需要有一个排序集),即使我将 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);
您的问题似乎是您有两个不同的 'value semantics' 到 Node
。
一个是operator<
暴露的,不受add_neighbour
的影响。这是 set
需要的,以保持秩序,它通过 Node
const
.
来强制执行
另一种是classAPI暴露的,其中set_id
和add_neighbour
都会改变值。
为了保持您的排序 set
,您不得允许节点的 ID 在节点进入集合后发生更改。但是你可以允许邻居改变。
所以我建议你制作 neighbours
set
mutable
,制作 add_neighbour
private
和 const
,然后制作 Graph
Node
个 friend
。
这就是 mutable
为您提供的,不属于某个类型的 'value' 的数据成员。请注意,这意味着您表示持有 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);
(...)
};
现在您拥有的是 public,不在 Graph
的 set
中的 Node
实例的非常量变元,以及一个额外的变元Graph
用于更改 set
中 Node
的邻居。
所以只有Graph
可以做到
const Node b;
b.add_neighbour(nullptr);
如果实在不信任Graph
,可以把private
const
add_neighbour
换成内class
,换成static add_neighbour(Node* node, Node* neighbour
方法,因为内部 class
可以隐式访问外部 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 中的 public
get_node_by_id
方法。您实际上可能想要公开某种迭代器,而不是原始 Node*
,并使 Node
成为 Graph 的私有内部 class。
或者甚至只是将整个 Node
概念替换为 std::map<int,std::set<int>>
...
但这取决于您的实际使用情况。
虽然 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>
可用了。
注意。反思一下,从 set 更改为 map 产生与 TBBle 的答案几乎相同的区别:您将 Node 拆分为 const 和 mutable 部分。使用此解决方案查找更清晰(您可以通过构造一个假节点作为 set::find
的键来重新获得对数时间查找,但它仍然有点不优雅),并且对象标识使用其他解决方案稍微更清晰。
我有一个似乎无法解决的常量正确性问题。这是我的程序的结构:
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()
,我想 return 一个指向给定节点(类型 Node
)的指针。但这样做似乎是不可能的,因为 std::set
隐式地将我的 Node 类型对象转换为 const Node
对象,而我无法从 const
对象中获取 non-const pointer
。
但是,我不能将其他所有设置都设置为 const Node
(这会解决问题),因为我想从 Graph::add_edge()
调用 Node::add_neighbour()
,但每当我这样做时,我的编译器说我可能违反了 node_list
集合中元素的 const
ness(需要有一个排序集),即使我将 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);
您的问题似乎是您有两个不同的 'value semantics' 到 Node
。
一个是operator<
暴露的,不受add_neighbour
的影响。这是 set
需要的,以保持秩序,它通过 Node
const
.
另一种是classAPI暴露的,其中set_id
和add_neighbour
都会改变值。
为了保持您的排序 set
,您不得允许节点的 ID 在节点进入集合后发生更改。但是你可以允许邻居改变。
所以我建议你制作 neighbours
set
mutable
,制作 add_neighbour
private
和 const
,然后制作 Graph
Node
个 friend
。
这就是 mutable
为您提供的,不属于某个类型的 'value' 的数据成员。请注意,这意味着您表示持有 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);
(...)
};
现在您拥有的是 public,不在 Graph
的 set
中的 Node
实例的非常量变元,以及一个额外的变元Graph
用于更改 set
中 Node
的邻居。
所以只有Graph
可以做到
const Node b;
b.add_neighbour(nullptr);
如果实在不信任Graph
,可以把private
const
add_neighbour
换成内class
,换成static add_neighbour(Node* node, Node* neighbour
方法,因为内部 class
可以隐式访问外部 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 中的 public
get_node_by_id
方法。您实际上可能想要公开某种迭代器,而不是原始 Node*
,并使 Node
成为 Graph 的私有内部 class。
或者甚至只是将整个 Node
概念替换为 std::map<int,std::set<int>>
...
但这取决于您的实际使用情况。
虽然 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>
可用了。
注意。反思一下,从 set 更改为 map 产生与 TBBle 的答案几乎相同的区别:您将 Node 拆分为 const 和 mutable 部分。使用此解决方案查找更清晰(您可以通过构造一个假节点作为 set::find
的键来重新获得对数时间查找,但它仍然有点不优雅),并且对象标识使用其他解决方案稍微更清晰。