关于const(成员和函数)的建议

Advice about const (member and function)

提问人: 提问时间:3/28/2023 最后编辑:General Grievance 更新时间:3/31/2023 访问量:82

问:

我正在研究树探索,我为节点实现了一个类“CNode”,其中每个节点都有一个指向其父节点的指针成员,我的问题是关于 const 函数和 const 成员,请参阅下面的代码。

所以下面是 main 和 class 的简化版本。

class CNode
{
private:
    int                 m_nID;
    CNode*              m_pParent;

public:

    CNode() : m_nID(0), m_pParent(nullptr) {}
    CNode(int n, CNode* pParent) : m_nID(n), m_pParent(pParent) {}

    CNode*  GetParent() const { return m_pParent; }
    void    PrintID() const { cout << m_nID << endl; }

    CNode* CreateChild(int n)
    {
        CNode* pNode = new CNode(n, this);
        return pNode;
    }
};


int main()
{
    CNode* pNodeRoot  = new CNode(0, nullptr);
    CNode* pNode = new CNode(1, pNodeRoot);

    while (pNode != nullptr)
    {
        pNode->PrintID();
        pNode = pNode->GetParent();
    }

    delete pNodeRoot;
    delete pNode;

    return 0;
}

所以这是有效的,但我不满意,因为成员函数:

CNode* CreateChild(int n)

应该是 const,所以让我们把它写成 const,但因为它使用“this”,所以我必须把成员“m_pParent”const(这似乎很正常,因为在我的情况下,节点不会改变父节点),最后它看起来像这样:

class CNode
{
private:
    int                 m_nID;
    const CNode*        m_pParent;

public:

    CNode() : m_nID(0), m_pParent(nullptr) {}
    CNode(int n, CNode* pParent) : m_nID(n), m_pParent(pParent) {}

    CNode* CreateChild(int n) const
    {
        CNode* pNode = new CNode(n, const_cast<CNode*>(this));
        return pNode;
    }

    const CNode*  GetParent() const { return m_pParent; }
    void          PrintID() const { cout << m_nID << endl; }
};

然后我必须使用const_cast更改 main 中的一行:

pNode = const_cast<CNode*>(pNode->GetParent());

它也在起作用。

两个版本都有效,我希望您的建议知道:

  • 什么是最佳实践,
  • 如果使用const_cast可以或不可以(我认为这是为了尽可能避免),
  • 如果有另一种方法(更简单或更整洁)来做到这一点。

谢谢。

C++(英语:C++) 常数 成员 常播

评论

4赞 paddy 3/28/2023
基本的经验法则是,如果你必须用它来编译你的程序,你几乎总是做错了什么。它的工作原理与你想象的相反。它删除 const 限定符,而不是添加它们。const_cast
2赞 Friedrich 3/28/2023
“节点不会更改父节点” - 没错。这就是为什么应该向 ctor 提出论点.这反过来又会使多余的东西变得多余。pParentconstconst_cast
3赞 MSalters 3/28/2023
“因为它使用的是”this“,所以我必须把成员 const”——不,这个想法是错误的,接下来的一切似乎都基于这个错误的前提。请注意,在 中是 ,但不是 !这是一个 constCNode *'m_pParentCNode* CreateChild(int n) constm_Parentconst*m_ParentCNode *const``, not a
0赞 3/29/2023
@Friedrich 谢谢好的,它应该是常量,但我不明白如何摆脱“CreateChild”中的const_cast。
0赞 3/29/2023
@MSalters 谢谢,是的,不清楚,所以我刚刚读了一篇文章“”const X* p“、”X* const p“之间的区别,我需要一遍又一遍地阅读......但到目前为止,如果没有const_cast,我不明白如何管理“这个”问题。

答:

0赞 Friedrich 3/29/2023 #1

不能让一个成员同时是可变的。你必须选择任何一个并承担后果。恒常性有点病毒式的东西。如果在任何地方公开为非指针,则它一定不是 。更改值是未定义的行为。constm_pParentconstconstconst

然而,你可以随心所欲地投下心来。因此,您可以拥有一个可变对象并决定只分发一个指针。constCNode* m_pParentconst

It is possible to overload on constness, so you could have two getters like this:

CNode*       m_pParent; // no const before the *!

const CNode* GetParent() const;
CNode*       GetParent();

The non- overload could only be called on a non-.constconst CNode

One of the reasons was introduced in the first place was to keep programmers from accidentally changing their own variables. See it as a tool in your tool box. So when the compiler complains about const violations, you should listen.const

In your case, it was because the child's would expose a mutable pointer and thus violate constness.GetParent()

Let's assume there's non- method and look at the following code:constCNode::SetID(int)

const CNode foo; // can only call const methods on foo
foo.SetID(123); // ERROR! const violation!
foo.CreateChild(1)->GetParent()->SetID(123); // ERROR! you would change foo through its child.

Because it was brought up in a comment: the meaning of in relation to is: . It's in alphabetical order (data, pointer) if that serves as a mnemonic.const*const data * const pointer

下面是代码片段的 const-correct 版本,其中包含:const CNode* m_pParent

class CNode
{
private:
    int                 m_nID;
    const CNode* const  m_pParent; // This is just a freebie.

public:

    CNode() : m_nID(0), m_pParent(nullptr) {}
    // Ctor now accepts a const CNode*. Everything is const now, no need for a const_cast
    CNode(int n, const CNode* pParent) : m_nID(n), m_pParent(pParent) {}

    CNode* CreateChild(int n) const
    {
        CNode* pNode = new CNode(n, this); // We use "this" without a const_cast :)
        return pNode;
    }

    const CNode*  GetParent() const { return m_pParent; }
    void          PrintID() const { std::cout << m_nID << "\n"; }
};

或者自己尝试一下 https://godbolt.org/z/4xs15P38a

另一方面,如果你需要有一个,从而向父级公开一个可变指针,那么就不能是简单明了的。CNode* GetParent()CreateChild()const

你又问了三个问题:

什么是最佳实践

尽可能使用,但不要超过这个频率。const

使用const_cast是否可以(我认为这是为了尽可能避免)

是的。根据经验:避免它!它是语言规范的一部分,并且有合法的用途。它比 C 型铸件更可取。

如果有另一种方法(更简单或更整洁)来做到这一点。

你必须决定是否要成为,并遵循其中一个决定以及它带来的一切。m_pParentconst

评论中提出的新问题的解决方案:https://godbolt.org/z/1crbxMxco

如果方法返回 a,则需要 a 来分配返回值。只有一个隐式演员表(出于显而易见的原因)。const T*const T*const

评论

0赞 3/30/2023
谢谢,是的,我看到它正在工作,但是在节点上循环时,我仍然有问题(const_cast需要),我尝试了godbolt(非常好的工具;)):https://godbolt.org/z/GsY19hza3
0赞 Friedrich 3/30/2023
@Calivernon编辑了我的问题,以回答您在评论中提出的新的和不同的问题。以后,请发布新问题。更好的是,买一本关于C++的好书,读一读关于恒常性的书。
0赞 3/30/2023
感谢您的帮助,您部分回答了我最初的问题,但我的第一个问题尚未回答:“如何在不使用const_cast的情况下正确放置成员配置”这是我提出的问题,并在第一篇文章中提供了详细信息。 谢谢。
1赞 3/31/2023
谢谢你,它非常清楚,它解释了一切并解决了我所有的问题。