Constness with standard containters of pointers

Discussion in 'C++' started by Javier, May 16, 2008.

  1. Javier

    Javier Guest

    Hello,

    I have some cuestions about constness with standard containers and
    pointers.
    Supose I have a list of pointers to some class B:

    std::list< B * > > list;

    I have readed that constness in std::list is the same that it is in C
    arrays (const std::list makes const both the list and its content):

    const std::list< B * > > constList;

    I can't do:

    constList->push_back(x);

    But, is it still possible to call any non-const member funcion of B?:

    void B::nonConstMemberFuncion();

    As in:

    constList.begin()->nonConstMemberFuncion();

    a) It is possible. I don't want the user to add elements nor modify
    any of the list. Which is the correct way of doing this?

    const std::list< const B * const > > constList;
    const std::list< const B * > constList;

    b) It is not possible. Then, I suppose that the next sentences are the
    same:

    const std::list < B > constList;
    const std::list < const B > constList;

    If I have the next class:

    class A {
    public:
    std::list< B * > & list() { return m_list; };
    private:
    std::list< B * > m_list;
    };

    How I add a const version for A::list()?

    What about if I have a smart_pointer instead of a C pointer?

    const std::list< boost::shared_ptr< B > > constList =
    something();
    constList.begin()->nonConstMemberFunction();

    Thanks.
    Javier, May 16, 2008
    #1
    1. Advertising

  2. Hi!

    Javier schrieb:
    > const std::list< const B * > constList;


    This is the way to go: "const B *" makes the "B" const, thus only const
    functions can be called. The "const std::list" makes the list immutable,
    thus you need to initialize it during construction.

    Regards,
    Frank
    Frank Birbacher, May 16, 2008
    #2
    1. Advertising

  3. Javier

    Javier Guest

    On 16 mayo, 17:20, Frank Birbacher <> wrote:
    >
    > > const std::list< const B * > constList;

    >
    > This is the way to go: "const B *" makes the "B" const, thus only const
    > functions can be called. The "const std::list" makes the list immutable,
    > thus you need to initialize it during construction.
    >


    Ok, then if I have:

    class A
    {
    public:
    ...
    std::list< B * > & getList() { return m_list; };
    const std::list< const B * > & getList() const
    { ...???... };

    private:
    std::list< B * > m_list;
    }

    What should I write in the const version of "getList()"?

    What about the conversion from "std::list< B * >" to "const std::list<
    const B * >"... The second const changes the template argument so, is
    it a different type?
    Javier, May 16, 2008
    #3
  4. Javier

    James Kanze Guest

    On 16 mai, 19:29, Victor Bazarov <> wrote:
    > Javier wrote:
    > > [..] if I have:


    > > class A
    > > {
    > > public:
    > > ...
    > > std::list< B * > & getList() { return m_list; };
    > > const std::list< const B * > & getList() const
    > > { ...???... };


    > > private:
    > > std::list< B * > m_list;
    > > }


    > > What should I write in the const version of "getList()"?


    > You can't do much there. list<T> and list<const T> are
    > different types and cannot be converted into each other.


    That's not quite true. There's nothing to stop you from writing
    something like:

    std::list< B const* > const A::getList() const
    {
    return std::list< B const* >( m_list.begin(), m_list.end() ) ;
    }

    > > What about the conversion from "std::list< B * >" to "const
    > > std::list< const B * >"... The second const changes the
    > > template argument so, is it a different type?


    > Yep.


    But the STL is designed so that you can convert any type of sequence
    to
    any other type, as long as the contained elements are
    convertible. I use this rather regularly for initializations,
    but it works generally.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, May 16, 2008
    #4
  5. Javier

    Javier Guest

    >
    > The OP had the function returning a _reference_.
    >


    That's the point. I want to return a reference to the member
    attribute, but avoiding the user to make any changes to the container
    and its content.

    >
    > I believe we use the term "convert" overloadedly here. Just as
    > much as one can "convert" an int into a C string...


    I want to "cast" not to create a new object and return it (if it is
    possible). If not, the solution posted by James is enough for me (I
    think):

    std::list< B const* > const A::getList() const
    {
    return std::list< B const* >( m_list.begin(), m_list.end() ) ;
    }

    Thanks all.
    Javier, May 17, 2008
    #5
  6. Javier

    Ian Collins Guest

    Javier wrote:
    >
    > If I have the next class:
    >
    > class A {
    > public:
    > std::list< B * > & list() { return m_list; };
    > private:
    > std::list< B * > m_list;
    > };
    >
    > How I add a const version for A::list()?
    >
    > What about if I have a smart_pointer instead of a C pointer?
    >
    > const std::list< boost::shared_ptr< B > > constList =
    > something();
    > constList.begin()->nonConstMemberFunction();
    >

    The obvious question is, why? If the list is private to the class, why
    would you want to expose it (as a constant or not) to users of the
    class? This looks like poor design to me.

    If the class is a wrapper for the list and you only want to grant const
    access to users, how about providing that access through const iterators?

    typedef std::list<B*>::const_iterator const_iterator;

    const_iterator begin() const { return m_list.begin(); }
    const_iterator end() const { return m_list.end(); }

    --
    Ian Collins.
    Ian Collins, May 17, 2008
    #6
  7. Javier

    James Kanze Guest

    Victor Bazarov a écrit :
    > James Kanze wrote:
    > > On 16 mai, 19:29, Victor Bazarov <> wrote:
    > >> Javier wrote:
    > >>> [..] if I have:


    > >>> class A
    > >>> {
    > >>> public:
    > >>> ...
    > >>> std::list< B * > & getList() { return m_list; };
    > >>> const std::list< const B * > & getList() const
    > >>> { ...???... };


    > >>> private:
    > >>> std::list< B * > m_list;
    > >>> }


    > >>> What should I write in the const version of "getList()"?


    > >> You can't do much there. list<T> and list<const T> are
    > >> different types and cannot be converted into each other.


    > > That's not quite true. There's nothing to stop you from writing
    > > something like:


    > > std::list< B const* > const A::getList() const
    > > {
    > > return std::list< B const* >( m_list.begin(), m_list.end() ) ;
    > > }


    > The OP had the function returning a _reference_.


    A const reference. But that's not the point. You can easily
    convert a list< T > to a list< T const >. You can also bind the
    results of the conversion to a const reference (although in this
    case, all that it will give you is a dangling reference). So
    the probably solution in his case is to redeclare the function
    to return an object, and use the conversion.

    > >>> What about the conversion from "std::list< B * >" to "const
    > >>> std::list< const B * >"... The second const changes the
    > >>> template argument so, is it a different type?


    > >> Yep.


    > > But the STL is designed so that you can convert any type of
    > > sequence to any other type, as long as the contained
    > > elements are convertible. I use this rather regularly for
    > > initializations, but it works generally.


    > I believe we use the term "convert" overloadedly here. Just
    > as much as one can "convert" an int into a C string...


    Yes and no. There's a very real sense that the above is a
    conversion, unlike the that of an int into a string. Like most
    C++ conversions, however, the result is an rvalue---a copy of
    the initial object, and not the object itself.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, May 17, 2008
    #7
  8. Javier

    James Kanze Guest

    On 17 mai, 02:46, Javier <> wrote:
    > > The OP had the function returning a _reference_.


    > That's the point. I want to return a reference to the member
    > attribute, but avoiding the user to make any changes to the
    > container and its content.


    The question then is why? Why not a copy?

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, May 17, 2008
    #8
  9. Javier

    Kai-Uwe Bux Guest

    Javier wrote:

    > Hello,
    >
    > I have some cuestions about constness with standard containers and
    > pointers.
    > Supose I have a list of pointers to some class B:
    >
    > std::list< B * > > list;
    >
    > I have readed that constness in std::list is the same that it is in C
    > arrays (const std::list makes const both the list and its content):
    >
    > const std::list< B * > > constList;
    >
    > I can't do:
    >
    > constList->push_back(x);
    >
    > But, is it still possible to call any non-const member funcion of B?:
    >
    > void B::nonConstMemberFuncion();
    >
    > As in:
    >
    > constList.begin()->nonConstMemberFuncion();
    >
    > a) It is possible. I don't want the user to add elements nor modify
    > any of the list. Which is the correct way of doing this?
    >
    > const std::list< const B * const > > constList;
    > const std::list< const B * > constList;
    >
    > b) It is not possible. Then, I suppose that the next sentences are the
    > same:
    >
    > const std::list < B > constList;
    > const std::list < const B > constList;
    >
    > If I have the next class:
    >
    > class A {
    > public:
    > std::list< B * > & list() { return m_list; };
    > private:
    > std::list< B * > m_list;
    > };
    >
    > How I add a const version for A::list()?


    Your problem is that the list contains B* object and making those const
    (e.g. by refering to the list via a const reference) does not make the
    pointees const. You could fix that using a smart pointer that forwards
    constness:


    template < typename T >
    class const_forwarding_ptr {

    T * the_ptr;

    public:

    const_forwarding_ptr ( T * ptr )
    : the_ptr ( ptr )
    {}

    T & operator* ( void ) {
    return ( *the_ptr );
    }

    T const & operator* ( void ) const {
    return ( *the_ptr );
    }

    T * operator-> ( void ) {
    return ( the_ptr );
    }

    T const * operator-> ( void ) const {
    return ( the_ptr );
    }

    operator T * ( void ) {
    return ( the_ptr );
    }

    operator T const * ( void ) const {
    return ( the_ptr );
    }

    };


    #include <list>

    int main ( void ) {
    std::list< const_forwarding_ptr< int > > the_list;
    int * i_ptr = new int ( 5 );
    the_list.push_back( i_ptr );
    *(*(the_list.begin())) = 6;
    std::list< const_forwarding_ptr< int > > const & c_ref = the_list;
    // the following line does not compile:
    // *(*(c_ref.begin())) = 5;
    }




    > What about if I have a smart_pointer instead of a C pointer?
    >
    > const std::list< boost::shared_ptr< B > > constList =
    > something();
    > constList.begin()->nonConstMemberFunction();


    Depends on the smart pointer. With regard to shared_ptr from C++0X, it does
    not forward constness to the pointee but acts similar to a raw pointer.


    Best

    Kai-Uwe Bux
    Kai-Uwe Bux, May 17, 2008
    #9
  10. Javier

    James Kanze Guest

    On 17 mai, 11:44, Stuart Golodetz
    <> wrote:
    > James Kanze wrote:
    > > On 17 mai, 02:46, Javier <> wrote:
    > >>> The OP had the function returning a _reference_.


    > >> That's the point. I want to return a reference to the member
    > >> attribute, but avoiding the user to make any changes to the
    > >> container and its content.


    > > The question then is why? Why not a copy?


    > I'm assuming you've probably already anticipated this reply
    > and have an answer ready, but I'll oblige by putting my foot
    > in it anyway... :)


    > "Because if n is the size of the list, making a copy is a
    > Theta(n) operation, whereas simply returning a reference (were
    > it possible in this case) would have been constant time."


    In other words, you've already tried this solution, and found it
    too slow.

    The question then might be why you are using std::list, and not
    std::vector or std::deque? If you can use one of them,
    particularly std::vector, I think you'll find that your
    performance problem disappears. It's true that there will be n
    copies, but copying a pointer is ridiculously cheap; what's
    causing the slowdown is probably the fact that std::list does an
    allocation as well for each copy.

    Another alternative---probably the best in the long run, at any
    rate, is to define what the user wants to do with this list, and
    provide an interface directly in your object for doing it. If
    nothing else, you can easily provide iterators into it, by
    wrapping the std::list<>::iterator. (You might have a look at
    boost::iterator for this. If nothing else, it will save some
    typing.)

    > It could be argued though that you should consider the real
    > reason for returning the list when doing this sort of thing:
    > if you just want clients of the class to be able to iterate
    > over the list, then you might be better off returning an
    > iterator (*) than the list itself (thus avoiding exposing an
    > aspect of the class's implementation). This allows you to get
    > round the type problem with the list, since for a list element
    > type T, you can convert from a T* to a const T* without any
    > problems.


    Exactly. The real question is more along the lines of: what
    does the user do with the list (or the reference to the list)
    that it gets? And wouldn't it be sensible to make this possible
    directly from the interface of your class, so that you don't
    (directly or indirectly) expose the internals of your object,
    i.e. the fact that it uses an std::list<>.

    > (*) I don't necessarily mean "iterator" in the sense of a
    > C++-style iterator here - it's just a generic term for
    > something which will let you iterate over all the elements of
    > the list.


    For better or for worse, we're stuck with the C++ style
    iterators. It's what C++ programmers know best. So while I
    greatly prefer a GoF iterator, and will always provide an
    interface for it when reasonably possible, any iterator which
    might be used in generic code, or might be used by client code,
    will support the STL idiom, and any code that I write which
    might end up being generic (and thus need to iterate over an STL
    container) will use the STL iterators, even when my iterators
    support a simpler and more intuitive idiom.

    The problems with the STL iterators are well known, and
    Boost::iterator goes a long way in compensating for them. In
    the case where you are simply wrapping another iterator, to
    change the return type of operator*(), of course, it's fairly
    easy... to screw up:). An input iterator is trivial, but the
    operator* of a forward iterator must return a reference, so you
    have to cache the converted value somewhere. (Technically, I
    think that even that is wrong, since the reference is required
    to be in the container. But if the iterator doesn't allow
    mutation, it's a silly requirement. Still, it does mean that
    &*c.begin() != &*c.begin().)

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, May 18, 2008
    #10
    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. Martin Magnusson

    Casting away constness

    Martin Magnusson, Nov 17, 2003, in forum: C++
    Replies:
    1
    Views:
    384
    tom_usenet
    Nov 17, 2003
  2. Richard Hayden
    Replies:
    1
    Views:
    742
    Rob Williscroft
    Nov 23, 2003
  3. Trevor Lango

    Casting Away Constness

    Trevor Lango, Jan 2, 2004, in forum: C++
    Replies:
    15
    Views:
    2,397
    Ron Natalie
    Jan 2, 2004
  4. Replies:
    14
    Views:
    828
    Ian Collins
    Apr 4, 2006
  5. cerr

    pointers, pointers, pointers...

    cerr, Apr 7, 2011, in forum: C Programming
    Replies:
    12
    Views:
    674
Loading...

Share This Page