I am studying tree exploration, I implemented a class "CNode" for the nodes where each node has a pointer member to point to his parent, my question is about a const function and a const member, see below in my code.
So below is a simplified version of the main and of the 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;
}
So that is working but I am not satisfied because the member function:
CNode* CreateChild(int n)
should be const, so let's put it const but because it is using "this" then I have to put the member "m_pParent" const (which seems normal as in my case the node won't change parent), at the end it looks like that:
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; }
};
and then I have to change one line in the main using a const_cast:
pNode = const_cast<CNode*>(pNode->GetParent());
and it is working as well.
Both versions are working, I would like your advice to know:
- what is the best practice,
- if using const_cast is ok or not (I thought that was to avoid as much as possible),
- if there is another way (simpler or neater) to do it.
Thank you.
You cannot have a member be both
constand mutable at the same time. You have to pick either and live with the consequences. Constness is a bit of a viral thing. If you exposem_pParentas a non-constpointer anywhere, it must not beconst. Changing aconstvalue is undefined behavior.You may, however, cast to
constto your heart's desire. So you can have a mutableCNode* m_pParentand decide to only hand out aconstpointer.It is possible to overload on constness, so you could have two getters like this:
The non-
constoverload could only be called on a non-const CNode.One of the reasons
constwas 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.In your case, it was because the child's
GetParent()would expose a mutable pointer and thus violate constness.Let's assume there's non-
constmethodCNode::SetID(int)and look at the following code:Because it was brought up in a comment: the meaning of
constin relation to*is:const data * const pointer. It's in alphabetical order (data, pointer) if that serves as a mnemonic.Below is a const-correct version of your code snippet with a
const CNode* m_pParent:Or try it yourself on https://godbolt.org/z/4xs15P38a
If, on the other hand, you need to have a
CNode* GetParent()and thus expose a mutable pointer to the parent, thenCreateChild()cannot beconst, plain and simple.You asked three more questions:
Use
constwherever possible but not more often than that.Yes. As a rule of thumb: avoid it! It is a part of the language specification and there are legitimate uses for it. It is preferable to C-style casts.
You have to decide if
m_pParentis to beconstor not and go with one of the decisions and everything it brings with it.Solution to the new problem brought up in the comment: https://godbolt.org/z/1crbxMxco
If a method returns a
const T*you need aconst T*to assign the return value to. There's only an implicit cast toconst(for obvious reasons).