const_cast in constructors to init const members

Discussion in 'C++' started by Erik Wikström, Feb 15, 2008.

  1. I have a base-class that have some protected members which will be
    initialised during construction, however since no derived classes (or
    members of the base-class) should change the values of those member I
    like to make the const. Unfortunately some of them are not trivial to
    initialise (i.e. they can not just be initialised from a value passed
    to the constructor. There are two ways I can construct these members,
    the first is by using helper-functions:

    class Bar {
    // Members
    const size_t m_nr;
    const std::vector<size_t> m_indices;

    // Helper function
    std::vector<size_t> getIndices(const std::vector<size_t>& v,
    size_t n) {
    std::vector<size_t> vec;
    for (size_t i = 0; i < v.size(); ++i) {
    if (v == n) {
    vec.push_back(i);
    }
    }
    return vec;
    }

    public:
    // Constructor
    Bar(size_t n, const std::vector<size_t>& v)
    : m_nr(n), m_indices(getIndices(v, n)) { }
    };

    This way works but there are two things that I'm not particularly fond
    of: I have to write a function(per member) that will only be used
    once, and it can become a bit messy when the initialisation of member
    B relies on member A already being initialised.

    The alternative is to use const_cast in the constructor to allow it to
    make modifications to the const members:

    class Foo {
    // Members
    const size_t m_nr;
    const std::vector<size_t> m_indices;

    public:
    // Constructor
    Foo(size_t n, const std::vector<size_t>& v)
    : m_nr(n)
    {
    for (size_t i = 0; i < v.size(); ++i) {
    if (v == m_nr) {
    const_cast<std::vector<size_t>& >(m_indices).push_back(i);
    }
    }
    }
    };

    The problem with this approach is that it is using const_cast (with
    ugly syntax as a result) which I'm not particularly fond of either,
    but on the up-side it is easier to read and you do not have to worry
    about initialisation order. Is this (modifying the const members)
    always safe or is this undefined behaviour? FAQ 18.13* talks a bit
    about this but I'm not sure if it applies since it talks about
    modifying members in a const member-function.

    * http://www.parashift.com/c -faq-lite/const-correctness.html#faq-18.13

    --
    Erik Wikström
    Erik Wikström, Feb 15, 2008
    #1
    1. Advertising

  2. * Erik Wikström:
    > I have a base-class that have some protected members which will be
    > initialised during construction, however since no derived classes (or
    > members of the base-class) should change the values of those member I
    > like to make the const. Unfortunately some of them are not trivial to
    > initialise (i.e. they can not just be initialised from a value passed
    > to the constructor. There are two ways I can construct these members,
    > the first is by using helper-functions:
    >
    > class Bar {
    > // Members
    > const size_t m_nr;
    > const std::vector<size_t> m_indices;
    >
    > // Helper function
    > std::vector<size_t> getIndices(const std::vector<size_t>& v,
    > size_t n) {
    > std::vector<size_t> vec;
    > for (size_t i = 0; i < v.size(); ++i) {
    > if (v == n) {
    > vec.push_back(i);
    > }
    > }
    > return vec;
    > }
    >
    > public:
    > // Constructor
    > Bar(size_t n, const std::vector<size_t>& v)
    > : m_nr(n), m_indices(getIndices(v, n)) { }
    > };
    >
    > This way works but there are two things that I'm not particularly fond
    > of: I have to write a function(per member) that will only be used
    > once, and it can become a bit messy when the initialisation of member
    > B relies on member A already being initialised.
    >
    > The alternative is to use const_cast in the constructor to allow it to
    > make modifications to the const members:
    >
    > class Foo {
    > // Members
    > const size_t m_nr;
    > const std::vector<size_t> m_indices;
    >
    > public:
    > // Constructor
    > Foo(size_t n, const std::vector<size_t>& v)
    > : m_nr(n)
    > {
    > for (size_t i = 0; i < v.size(); ++i) {
    > if (v == m_nr) {
    > const_cast<std::vector<size_t>& >(m_indices).push_back(i);
    > }
    > }
    > }
    > };
    >
    > The problem with this approach is that it is using const_cast (with
    > ugly syntax as a result) which I'm not particularly fond of either,
    > but on the up-side it is easier to read and you do not have to worry
    > about initialisation order. Is this (modifying the const members)
    > always safe or is this undefined behaviour?


    Technically in-practice it should be OK since the vector doesn't know
    that it's const, and was non-const during its own construction. But
    formally I don't know and I don't think you should care. Because, you
    shouldn't employ any of the two approaches above.

    Instead,

    class Foo {
    private:
    Foo( Foo const& );
    Foo& operator=( Foo const& );

    protected:

    struct Indices {
    size_t m_nr;
    std::vector<size_t> m_values;

    Indices(size_t n, const std::vector<size_t>& v)
    : m_nr(n)
    {
    for (size_t i = 0; i < v.size(); ++i) {
    if (v == m_nr) {
    m_values.push_back(i);
    }
    }
    }

    size_t nr() const { return m_nr; }
    size_t size() const { return m_values.size(); }
    size_t operator[]( size_t i ) const { return m_values; }
    };

    const Indices m_indices;

    public:
    Foo(size_t n, const std::vector<size_t>& v)
    : m_indices(n, v)
    {}
    };

    The "protected:" here from your requirements spec.


    > FAQ 18.13* talks a bit
    > about this but I'm not sure if it applies since it talks about
    > modifying members in a const member-function.
    >
    > * http://www.parashift.com/c -faq-lite/const-correctness.html#faq-18.13


    Well, hm, uh.

    (Actually I haven't looked at it)


    Cheers, & hth.,

    - Alf

    --
    A: Because it messes up the order in which people normally read text.
    Q: Why is it such a bad thing?
    A: Top-posting.
    Q: What is the most annoying thing on usenet and in e-mail?
    Alf P. Steinbach, Feb 15, 2008
    #2
    1. Advertising

  3. On Feb 15, 10:44 am, "Alf P. Steinbach" <> wrote:
    > * Erik Wikström:
    >
    >
    >
    > > I have a base-class that have some protected members which will be
    > > initialised during construction, however since no derived classes (or
    > > members of the base-class) should change the values of those member I
    > > like to make the const. Unfortunately some of them are not trivial to
    > > initialise (i.e. they can not just be initialised from a value passed
    > > to the constructor. There are two ways I can construct these members,
    > > the first is by using helper-functions:

    >
    > > class Bar {
    > > // Members
    > > const size_t m_nr;
    > > const std::vector<size_t> m_indices;

    >
    > > // Helper function
    > > std::vector<size_t> getIndices(const std::vector<size_t>& v,
    > > size_t n) {
    > > std::vector<size_t> vec;
    > > for (size_t i = 0; i < v.size(); ++i) {
    > > if (v == n) {
    > > vec.push_back(i);
    > > }
    > > }
    > > return vec;
    > > }

    >
    > > public:
    > > // Constructor
    > > Bar(size_t n, const std::vector<size_t>& v)
    > > : m_nr(n), m_indices(getIndices(v, n)) { }
    > > };

    >
    > > This way works but there are two things that I'm not particularly fond
    > > of: I have to write a function(per member) that will only be used
    > > once, and it can become a bit messy when the initialisation of member
    > > B relies on member A already being initialised.

    >
    > > The alternative is to use const_cast in the constructor to allow it to
    > > make modifications to the const members:

    >
    > > class Foo {
    > > // Members
    > > const size_t m_nr;
    > > const std::vector<size_t> m_indices;

    >
    > > public:
    > > // Constructor
    > > Foo(size_t n, const std::vector<size_t>& v)
    > > : m_nr(n)
    > > {
    > > for (size_t i = 0; i < v.size(); ++i) {
    > > if (v == m_nr) {
    > > const_cast<std::vector<size_t>& >(m_indices).push_back(i);
    > > }
    > > }
    > > }
    > > };

    >
    > > The problem with this approach is that it is using const_cast (with
    > > ugly syntax as a result) which I'm not particularly fond of either,
    > > but on the up-side it is easier to read and you do not have to worry
    > > about initialisation order. Is this (modifying the const members)
    > > always safe or is this undefined behaviour?

    >
    > Technically in-practice it should be OK since the vector doesn't know
    > that it's const, and was non-const during its own construction. But
    > formally I don't know and I don't think you should care. Because, you
    > shouldn't employ any of the two approaches above.
    >
    > Instead,
    >
    > class Foo {
    > private:
    > Foo( Foo const& );
    > Foo& operator=( Foo const& );
    >
    > protected:
    >
    > struct Indices {
    > size_t m_nr;
    > std::vector<size_t> m_values;
    >
    > Indices(size_t n, const std::vector<size_t>& v)
    > : m_nr(n)
    > {
    > for (size_t i = 0; i < v.size(); ++i) {
    > if (v == m_nr) {
    > m_values.push_back(i);
    > }
    > }
    > }
    >
    > size_t nr() const { return m_nr; }
    > size_t size() const { return m_values.size(); }
    > size_t operator[]( size_t i ) const { return m_values; }
    > };
    >
    > const Indices m_indices;
    >
    > public:
    > Foo(size_t n, const std::vector<size_t>& v)
    > : m_indices(n, v)
    > {}
    > };


    Yes, using a wrapper is certainly nice, but then I would have to write
    one wrapper class for each const member (unless there are two that can
    be initialised in the same way), which can be a bit tiresome
    (especially if I need to support much of the container's interface).
    And I still have to worry about the order of initialisation when they
    are dependent on each other.

    --
    Erik Wikström
    Erik Wikström, Feb 15, 2008
    #3
  4. * Erik Wikström:
    > On Feb 15, 10:44 am, "Alf P. Steinbach" <> wrote:
    >> * Erik Wikström:
    >>
    >>
    >>
    >>> I have a base-class that have some protected members which will be
    >>> initialised during construction, however since no derived classes (or
    >>> members of the base-class) should change the values of those member I
    >>> like to make the const. Unfortunately some of them are not trivial to
    >>> initialise (i.e. they can not just be initialised from a value passed
    >>> to the constructor. There are two ways I can construct these members,
    >>> the first is by using helper-functions:

    [znip]

    >>> The alternative is to use const_cast in the constructor to allow it to
    >>> make modifications to the const members:

    [znip]

    >> Instead,
    >>
    >> class Foo {
    >> private:
    >> Foo( Foo const& );
    >> Foo& operator=( Foo const& );
    >>
    >> protected:
    >>
    >> struct Indices {
    >> size_t m_nr;
    >> std::vector<size_t> m_values;
    >>
    >> Indices(size_t n, const std::vector<size_t>& v)
    >> : m_nr(n)
    >> {
    >> for (size_t i = 0; i < v.size(); ++i) {
    >> if (v == m_nr) {
    >> m_values.push_back(i);
    >> }
    >> }
    >> }
    >>
    >> size_t nr() const { return m_nr; }
    >> size_t size() const { return m_values.size(); }
    >> size_t operator[]( size_t i ) const { return m_values; }
    >> };
    >>
    >> const Indices m_indices;
    >>
    >> public:
    >> Foo(size_t n, const std::vector<size_t>& v)
    >> : m_indices(n, v)
    >> {}
    >> };

    >
    > Yes, using a wrapper is certainly nice, but then I would have to write
    > one wrapper class for each const member (unless there are two that can
    > be initialised in the same way), which can be a bit tiresome
    > (especially if I need to support much of the container's interface).


    Well then, make the members non-const private and provide
    const-enforcing protected accessor functions.


    > And I still have to worry about the order of initialisation when they
    > are dependent on each other.


    You'll have to worry about that no matter which solution you land on,
    except by enforcing initialization order via inheritance chain of dummy
    classes.

    If initialization order really is a general problem then it indicates
    some design flaw, most likely lack of encapsulation in classes. Because
    the items that have initialization order dependencies are strongly
    connected and probably form some useful abstractions. So, for that,
    think design, not language-level technical solution.


    Cheers, & hth.,

    - Alf

    --
    A: Because it messes up the order in which people normally read text.
    Q: Why is it such a bad thing?
    A: Top-posting.
    Q: What is the most annoying thing on usenet and in e-mail?
    Alf P. Steinbach, Feb 15, 2008
    #4
    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. Tony Cheng
    Replies:
    1
    Views:
    8,209
    Juan T. Llibre
    Feb 24, 2006
  2. AlesD
    Replies:
    6
    Views:
    1,480
    Rolf Magnus
    Jul 12, 2004
  3. Replies:
    1
    Views:
    658
    Jules
    Aug 18, 2005
  4. Javier
    Replies:
    2
    Views:
    561
    James Kanze
    Sep 4, 2007
  5. news.aon.at
    Replies:
    11
    Views:
    646
    Ian Collins
    Jan 29, 2011
Loading...

Share This Page