How bad is making a field mutable?

  • Thread starter christopher diggins
  • Start date
C

christopher diggins

I wrote a dynamic matrix class similar to the one described in TCPL 3rd
Edition. Rather than define two separate iterators for const and non-const
scenarios I decided to be a lazy bastard and only have one and make the data
representation (a std::valarray) mutable instead. My question is, how bad is
that? Am I running the risk of undefined behaviour, or is the worst case
scenario simply const violation?
 
A

Alf P. Steinbach

* christopher diggins:
I wrote a dynamic matrix class similar to the one described in TCPL 3rd
Edition. Rather than define two separate iterators for const and non-const
scenarios I decided to be a lazy bastard and only have one and make the data
representation (a std::valarray) mutable instead. My question is, how bad is
that? Am I running the risk of undefined behaviour

Probably, if it's possible to originally declare a matrix with non-zero
size as constant.

Matrix<int> const m(2, 2);

*m.begin() = 1; // Probably compiles but UB.

or is the worst case scenario simply const violation?

Depends.

Const violation for an object originally declared const is UB.
 
D

Donovan Rebbechi

I wrote a dynamic matrix class similar to the one described in TCPL 3rd
Edition. Rather than define two separate iterators for const and non-const
scenarios I decided to be a lazy bastard

So far so good. Laziness is good.
and only have one and make the data
representation (a std::valarray) mutable instead.

No. Bad.

The smart way to be lazy would be to make your iterator a template class.

template <typename T, typename pointer> general_iterator {
....
};

typedef general_iterator<T,T*> iterator;
typedef general_iterator said:
My question is, how bad is that?

Very bad.
Am I running the risk of undefined behaviour,

Probably depends on what you do with it. One case where it gets you in a
lot of trouble is with threaded code. Modifying state behind the clients
back means that methods that look like they shouldn't require an exclusive
lock actually do require one. For example, it's reasonable to expect that
multiple concurrent readers are OK, but if those readers are secretly
writing, then it's a problem. I suppose you could require that every element
access of any kind required an exclusive lock, but I'm sure you can see why
that's unwieldy.
or is the worst case scenario simply const violation?

const violation is already very bad. It makes life very inconvenient when you
get bugs, because you can no longer trust the word "const" any more. "const"
is a contract of sorts. When you stop honoring it, it loses its relevance.

Cheers,
 
C

christopher diggins

Alf P. Steinbach said:
* christopher diggins:

Probably, if it's possible to originally declare a matrix with non-zero
size as constant.

Matrix<int> const m(2, 2);

*m.begin() = 1; // Probably compiles but UB.

So I have this:

template<typename T>
class Matrix {
mutable std::valarray<T> m;
public:
Matrix(int rows, int cols) : m(rows * cols) { }
T* begin() const { return &m[0]; }
...
}

Why does this lead to undefined behaviour? By stating that m is mutable,
does it not tell the compiler that m can be changed? If I understand you
correctly would this not mean that any modification of a mutable variable by
a const function is UB?

I am perhaps confused.

Thanks for your help!
 
A

Alf P. Steinbach

* christopher diggins:
Why does this lead to undefined behaviour? By stating that m is mutable,
does it not tell the compiler that m can be changed? If I understand you
correctly would this not mean that any modification of a mutable variable by
a const function is UB?

You're right, sorry.

Reference:

§7.1.5.1/4 states that "Except that any class member declared 'mutable' can
be modified, any attempt to modify a 'const' object during its lifetime
results in undefined behavior".

I am perhaps confused.

Thanks for your help!

Well, it was less than nothing, so thanks for the thanks!

But, anyway, consider not breaking constness.
 
C

christopher diggins

Donovan Rebbechi said:
So far so good. Laziness is good.


No. Bad.

The smart way to be lazy would be to make your iterator a template class.

template <typename T, typename pointer> general_iterator {
...
};

typedef general_iterator<T,T*> iterator;
typedef general_iterator<T,const T*> const_iterator;
[snip]

That is a great idea! And I appreciate the other comments too.

Thank you!
 
C

christopher diggins

So here is a generalized stride iterator, is it a correct random access
iterator?

template<class Iter_T>
class stride_iter
{
public:
// public typedefs
typedef typename std::iterator_traits<Iter_T>::value_type value_type;
typedef typename std::iterator_traits<Iter_T>::reference reference;
typedef typename std::iterator_traits<Iter_T>::difference_type
difference_type;
typedef typename std::iterator_traits<Iter_T>::pointer pointer;
typedef std::random_access_iterator_tag iterator_category;
typedef stride_iter self;

// constructors
stride_iter() : m(null), step(0) { };
explicit stride_iter(Iter_T x, difference_type n) : m(x), step(n) { }

// operators
self& operator++() { m += step; return *this; }
self operator++(int) { self tmp = *this; m += step; return tmp; }
reference operator[](difference_type n) { return m[n * step]; }
reference operator*() { return *m; }
friend bool operator==(const self& x, const self& y) { return x.m ==
y.m; }
friend bool operator!=(const self& x, const self& y) { return x.m !=
y.m; }
friend bool operator<(const self& x, const self& y) { return x.m <
y.m; }
private:
Iter_T m;
difference_type step;
};
 

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,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top