Const correctness for pointer data members

Q

quantdev2004

Hi all,

I have been deling with this kind of code:

class Foo
{
public:
void NonConstMethod()
{}
};

class Bar
{
public:
// [...] (Class simplified)

void NonLogicallyConstMethod() const
{
m_pBar->NonConstMethod();
}
private:
Foo* const m_pBar;

};

Although this is accepted by the compiler, and therefore const-correct,
I often find myself puzzled as to whether this is actually and
logically const-correct.
It just happens that NonLogicallyConstMethod() can invoke a const
method because of the way Bar is associated to Foo i.e. If I used a
class member variable this would not be allowed. The fact that the
pointer itself is const looks like a workaround (not unlike using
<mutable>).

What am I missing?
Thanks,
Quantdev2004
 
B

Bob Hairgrove

Hi all,

I have been deling with this kind of code:

class Foo
{
public:
void NonConstMethod()
{}
};

class Bar
{
public:
// [...] (Class simplified)

void NonLogicallyConstMethod() const
{
m_pBar->NonConstMethod();
}
private:
Foo* const m_pBar;

};

Although this is accepted by the compiler, and therefore const-correct,
I often find myself puzzled as to whether this is actually and
logically const-correct.
It just happens that NonLogicallyConstMethod() can invoke a const
method because of the way Bar is associated to Foo i.e. If I used a
class member variable this would not be allowed. The fact that the
pointer itself is const looks like a workaround (not unlike using
<mutable>).

What am I missing?
Thanks,
Quantdev2004

What you are missing is that the const-ness (or in C++ standard-speak,
the "cv-qualification") of the pointer has absolutely nothing to do
with the const-ness (or "cv-qualification") of the thing(s) it points
to. These are two totally and fundamentally different things.
 
V

Victor Bazarov

I have been deling with this kind of code:

class Foo
{
public:
void NonConstMethod()
{}
};

class Bar
{
public:
// [...] (Class simplified)

void NonLogicallyConstMethod() const
{
m_pBar->NonConstMethod();
}
private:
Foo* const m_pBar;

It's a constant pointer to a mutable (non-constant) Foo.
};

Although this is accepted by the compiler, and therefore const-correct,
I often find myself puzzled as to whether this is actually and
logically const-correct.

Unknown. Does 'Bar' *own* his copy of 'Foo', to which 'm_pBar' (BTW, do
you think that the name could be a bit corrected, like 'm_pFoo'?) is
pointing? If it does own it, then you're probably right, the logic is
a bit screwed up. If it does not own it, it probably does not matter.
It just happens that NonLogicallyConstMethod() can invoke a const
method

Isn't it actually vice versa: it can invoke a NON-const method?
> because of the way Bar is associated to Foo i.e. If I used a
class member variable this would not be allowed.

In that case the 'Foo' *object* itself would be const.
> The fact that the
pointer itself is const looks like a workaround (not unlike using
<mutable>).

What am I missing?

A good book, maybe? And thinking about what to allow and what not to
allow in your own classes...

V
 
M

mlimber

Hi all,

I have been deling with this kind of code:

class Foo
{
public:
void NonConstMethod()
{}
};

class Bar
{
public:
// [...] (Class simplified)

void NonLogicallyConstMethod() const
{
m_pBar->NonConstMethod();
}
private:
Foo* const m_pBar;

};

Although this is accepted by the compiler, and therefore const-correct,
I often find myself puzzled as to whether this is actually and
logically const-correct.
It just happens that NonLogicallyConstMethod() can invoke a const
method because of the way Bar is associated to Foo i.e. If I used a
class member variable this would not be allowed. The fact that the
pointer itself is const looks like a workaround (not unlike using
<mutable>).

What am I missing?
Thanks,
Quantdev2004

You're not missing anything. Constness is shallow. That is, C++ does
not transfer constness from the pointer to the pointee. (In your case,
m_pBar is always const, but if you removed that const, it would become
const in const methods, though the pointee would not).

There have been many discussions on this NG and others about why this
is or is not a violation of const-correctness. I find the behavior
non-intuitive and think constness should be transferred to the pointee
in these cases and overridden when necessary with mutable-like
declaration, but changing the language would break a good deal of
existing code.

To get the behavior you want, you can do something like this:

template <class T>
class DeepPtr
{
public:
DeepPtr( T *const ptr ) : m_ptr( ptr ) { assert(m_ptr); }
DeepPtr( DeepPtr<T>& ptr ) : m_ptr( ptr.m_ptr ) {}

// Standard access
T* operator->() { return m_ptr; }
T& operator*() { return *m_ptr; }
T& operator[]( ui32 n ) { return m_ptr[ n ]; }

// Make pointee const if pointer is const
T const* operator->() const { return m_ptr; }
T const& operator*() const { return *m_ptr; }
T const& operator[]( ui32 n ) const { return m_ptr[ n ]; }

private:
T * const m_ptr;

// Disabled methods
DeepPtr( const DeepPtr& );
DeepPtr& operator=( const DeepPtr& );
};

struct Foo
{
void NonConst() {};
};

struct Bar
{
Bar( Foo* pFoo ) : m_pFoo( pFoo ) {}
void Const() const { m_pFoo->NonConst(); } // Error!
private:
DeepPtr<Foo> m_pFoo;
};

Unfortunately, the disabled copy constructor and assignment operator
mean that such a pointer cannot be used in a standard container.

Cheers! --M
 
K

Karl Heinz Buchegger

Although this is accepted by the compiler, and therefore const-correct,
I often find myself puzzled as to whether this is actually and
logically const-correct.
It just happens that NonLogicallyConstMethod() can invoke a const
method because of the way Bar is associated to Foo i.e. If I used a
class member variable this would not be allowed. The fact that the
pointer itself is const looks like a workaround (not unlike using
<mutable>).

What am I missing?

foo* bar; A pointer. Neither the pointer not what it points to is const
foo* const bar; A pointer. The pointer itself is const. But not the object
the pointer points to. In other words: While it is illegal to
make the pointer point to another object, you can change the object
as often as you want

foo const * bar; A pointer. The pointer is not const, but the object the pointer points
to is. This means, the pointer can be reseated to point to different objects
but it is illegal to attempt to change the objects themselfs.

foo const * const bar; Another pointer. This time the pointer and what it points to is const.
Neither can the pointer be reseated to a different object, nor can the object
itself be changed through this pointer

const foo * const bar; identical to foo const * const bar;


const applies to the thing on its left, with the only exception if const is the leftmost specifier.
Then it applies to the thing on its right.

As for your class. Only the things in your class are 'protected' in a const member function. The
pointer
is a part of your class, but not the object the pointer points to.
 
M

mlimber

mlimber wrote:
[snip]
template <class T>
class DeepPtr
{
public:
DeepPtr( T *const ptr ) : m_ptr( ptr ) { assert(m_ptr); }
DeepPtr( DeepPtr<T>& ptr ) : m_ptr( ptr.m_ptr ) {}

// Standard access
T* operator->() { return m_ptr; }
T& operator*() { return *m_ptr; }
T& operator[]( ui32 n ) { return m_ptr[ n ]; }

// Make pointee const if pointer is const
T const* operator->() const { return m_ptr; }
T const& operator*() const { return *m_ptr; }
T const& operator[]( ui32 n ) const { return m_ptr[ n ]; }

private:
T * const m_ptr;

// Disabled methods
DeepPtr( const DeepPtr& );
DeepPtr& operator=( const DeepPtr& );
};
[snip]

BTW, I should have included <cassert> and ui32 should be translated to
size_t or unsigned int or whatever.

Cheers! --M
 
Q

quantdev2004

Unknown. Does 'Bar' *own* his copy of 'Foo', to which 'm_pBar' (BTW, do
you think that the name could be a bit corrected, like 'm_pFoo'?)

yes, it's m_pFoo;
is
pointing? If it does own it, then you're probably right, the logic is
a bit screwed up. If it does not own it, it probably does not matter.

Ownership it's a good discriminant, I didn't think about it!
Isn't it actually vice versa: it can invoke a NON-const method?

you're right again, sorry about the typos!
In that case the 'Foo' *object* itself would be const.

that's what I find strange the fact that a syntactic detail (having the
object on the heap and owned, as opposed to a class member variable) is
giving me the chance to avoid const correctness.

A good book, maybe? And thinking about what to allow and what not to
allow in your own classes...

As I said I have no problems understanding the syntax and semantics. I
take the point on ownership and lifetime management though.

Thanks for you input,
quantdev2004
 
M

mlimber

Victor Bazarov wrote:
[snip]
Unknown. Does 'Bar' *own* his copy of 'Foo', to which 'm_pBar' ... is
pointing? If it does own it, then you're probably right, the logic is
a bit screwed up. If it does not own it, it probably does not matter.
[snip]

I'd suggest that even if Bar does not *uniquely* own that Foo object,
it generally should still transfer its constness to the pointee.
Consider:

class Foo;

class Bar
{
Bar() : pFoo_( new Foo ) {}
~Bar() { delete pFoo_; }
// ...
private:
Foo* const pFoo;
};

Almost certainly, Bar uniquely owns the pointee of pFoo, and in such a
case, transferring the constness of Bar to pFoo should probably also
transfer it to the pointee of pFoo. (Exceptions to that transferring
rule might include caching mechanisms, semaphores, etc., which are
private and still preserve logical constness but not physical
constness.)

When it comes to shared ownership, I'd suggest the same sort of thing
should generally apply. Consider:

class A;

class B
{
B( A& a ) : a_( a ) {} // Obviously, B is not responsible
// for deleting a; possibly better
// would be to do some ref counting

void Const() const; // should a_ be modifiable here?
// ...

private:
A& a_;
};

With the same sort of exception cases (caching schemes etc.),
const-correctness seems to imply that *a_ should usually become const
inside B::Const() even though there is no unique ownership here.

In short, I think transferring constness to pointee members should be
the default in all cases and should be overridden with a mutable
extension. (Compare std::vector and arrays: the first is fully
const-correct as a member in a class, the second is not.)

Cheers! --M
 
M

mlimber

mlimber wrote:
[snip]
With the same sort of exception cases (caching schemes etc.),
const-correctness seems to imply that *a_ should usually become const
[snip]

Correction: *a_ should be simply a_ since it is a reference.
 
Q

quantdev2004

To get the behavior you want, you can do something like this:

[...]

Thanks for your contribution. I will give a go to your DeepPtr class.

Unfortunately, the disabled copy constructor and assignment operator
mean that such a pointer cannot be used in a standard container.

But you wouldn't be able to use "normal" const pointers either, isn't
it?
quantdev2004
 
M

mlimber

But you wouldn't be able to use "normal" const pointers either, isn't
it?

The pointees of member pointers (wheter the pointers are const or
non-const) and the referees of member references all have the same
issue with const-correctness. (Is that what you are asking?)

Cheers! --M
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top