如何对此进行持续纠正

How to have this const-corrected?

本文关键字:      更新时间:2023-10-16

我有一个恒常正确性问题,我似乎无法解决。这是我的程序结构:

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将我的 Node 类型对象隐式转换为const Node对象,并且我无法从const对象获取non-const pointer

但是,我不能将其他所有内容都设置为 const Node(这将解决问题),因为我想从 Graph::add_edge() 调用Node::add_neighbour(),但是每当我这样做时,我的编译器都会说我可能违反了node_list集中元素的const性(需要有一个排序集), 即使我将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);

您的问题似乎是您要Node两种不同的"值语义"。

一种是不受add_neighbour影响的operator<暴露。这是set需要的,以保持秩序,并通过Node const来强制执行。

另一个是由类 API 公开的,其中 set_idadd_neighbour 都会更改值。

为了保持排序set,您不得允许节点的 id 在集合中更改。但你可以允许邻居改变。

所以我建议你做neighbours set mutable,做add_neighbour privateconst,让Graph成为Nodefriend

这就是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);
        (...)
};

现在你拥有的是公共的、非常量突变器,用于不在Graph set中的Node实例,以及一个额外的突变器供Graph用来改变set Node的邻居

所以只有Graph能做到

const Node b;
b.add_neighbour(nullptr);

如果你真的不信任Graph,你可以用static add_neighbour(Node* node, Node* neighbour方法将private const add_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);

在这一点上,你应该遭受代码气味...问题是图形中的public get_node_by_id方法。实际上,您可能希望公开某种迭代器,而不是原始Node*,并Node成为Graph的私有内部类。

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

但这取决于您的实际用例。

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

您当前的Graph::get_node_by_id()正在使用线性搜索,因为set并没有真正提供您想要的查找。将密钥设为外部可以消除operator<过载,并且仍然可以获得更快、更自然的查找:map.find(id)

唯一丑陋的部分是,现在您的Node有一个内部 ID,该 ID 必须与外部密钥匹配。如果您从不使用id,除了在映射中查找节点,则可以将其完全删除。如果需要遵循图形边缘(相邻),然后检查 ID,则可以将指针集替换为一组映射迭代器,例如:

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

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


注意:经过反思,从集合到映射的更改会产生与TBBle的答案几乎相同的区别:您将节点拆分为常量和可变部分。使用此解决方案查找更干净(您可以通过构造一个假 Node 作为set::find键来重新获得对数时间查找,但它仍然有点不优雅),并且对象标识使用其他解决方案稍微干净一些。

相关文章:
  • 没有找到相关文章