Const correctness for pointer data members

Discussion in 'C++' started by quantdev2004@yahoo.co.uk, Oct 4, 2005.

  1. Guest

    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
    , Oct 4, 2005
    #1
    1. Advertising

  2. On 4 Oct 2005 07:13:50 -0700, wrote:

    >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.

    --
    Bob Hairgrove
    Bob Hairgrove, Oct 4, 2005
    #2
    1. Advertising

  3. wrote:
    > 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
    Victor Bazarov, Oct 4, 2005
    #3
  4. mlimber Guest

    wrote:
    > 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
    mlimber, Oct 4, 2005
    #4
  5. wrote:
    >
    >
    > 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.

    --
    Karl Heinz Buchegger
    Karl Heinz Buchegger, Oct 4, 2005
    #5
  6. mlimber Guest

    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
    mlimber, Oct 4, 2005
    #6
  7. Guest

    > 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!

    > > It just happens that NonLogicallyConstMethod() can invoke a const
    > > method

    >
    > Isn't it actually vice versa: it can invoke a NON-const method?


    you're right again, sorry about the typos!

    > > 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.


    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
    , Oct 4, 2005
    #7
  8. mlimber Guest

    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
    mlimber, Oct 4, 2005
    #8
  9. mlimber Guest

    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.
    mlimber, Oct 4, 2005
    #9
  10. Guest

    >
    > 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
    , Oct 4, 2005
    #10
  11. mlimber Guest

    wrote:
    [snip]
    > > 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?


    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
    mlimber, Oct 5, 2005
    #11
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Jim Strathmeyer

    stl list, const correctness

    Jim Strathmeyer, Mar 19, 2005, in forum: C++
    Replies:
    2
    Views:
    507
    Pete Becker
    Mar 20, 2005
  2. Matthias Kaeppler

    const-correctness and lambda expression

    Matthias Kaeppler, Apr 16, 2005, in forum: C++
    Replies:
    1
    Views:
    607
    Kanenas
    Apr 20, 2005
  3. Javier
    Replies:
    2
    Views:
    556
    James Kanze
    Sep 4, 2007
  4. fungus
    Replies:
    13
    Views:
    880
    fungus
    Oct 31, 2008
  5. puzzlecracker
    Replies:
    6
    Views:
    427
    mzdude
    Mar 20, 2009
Loading...

Share This Page