Sign in to follow this  
rozz666

[C++] Is it safe?

Recommended Posts

rozz666    896
Suppose you have:
template <typename T>
class array {
    // some stuff
    // no virtual methods
private:
    T *data;
    size_t size;
    uint *ref_cnt;
};

template <typename T>
class array<const T> {
    // similar stuff
    // no virtual methods
private:
    const T *data;
    size_t size;
    uint *ref_cnt;
};
Now, is it safe to cast pointers to array<T> to array<const T>? E.g.:
template <typename T>;
array<const T> array<const T>::cast(array<T>& rhs)
{
    return *reinterpret_cast<array<const T> *>(&rhs);
}

Share this post


Link to post
Share on other sites
ToohrVyk    1595
Depends on the "some stuff", which may alter the sequentiality of your private members.

As a whole, though, people usually prefer to use iterators instead to handle this kind of problem.

Share this post


Link to post
Share on other sites
rozz666    896
Quote:
Original post by Evil Steve
"Safe", yes. A good idea? No. Why not give array() a method to return a const version of itslef?


array<const T> has constructor array(const array<T>& );

However, technically every array<const T> is also an array<T>, so there should be no need for temprary object creation. E.g.



array<int> a;

void f1(const array<const int>& );
void f2(array<const int>& );


f1(a); // temporary object array<const int>
f2(a); // warning, and wrong anyways



Oh, now I see your point :-)
Making cast(array<int>& ) and cast(const array<int>& ) won't work.

But I'm thinking of a method to make it implicit.
Maybe:


template <typename T>
class array : public array<const T> {
// stuff with many const_cast's :-)
};

template <typename T>
class array<const T> {
protected:
const T *data;
size_t size;
uint *ref_cnt;
};

Share this post


Link to post
Share on other sites
ToohrVyk    1595
Quote:
Original post by rozz666
However, technically every array<const T> is also an array<T>


I cannot use an array<const T> as if it were an array<T>, because that would let me change its elements. Arguably, one could consider that an array<T> is an array<const T>, though, so I guess this is what you meant.

Again, however, iterators are a far more elegant way of handling this, by separating the physical storage (the container) from the const/non-const way of accessing the elements (the iterator).

Share this post


Link to post
Share on other sites
rozz666    896
Quote:
Original post by ToohrVyk
Quote:
Original post by rozz666
However, technically every array<const T> is also an array<T>


I cannot use an array<const T> as if it were an array<T>, because that would let me change its elements. Arguably, one could consider that an array<T> is an array<const T>, though, so I guess this is what you meant.

Yes, that's what I meant.
Quote:


Again, however, iterators are a far more elegant way of handling this, by separating the physical storage (the container) from the const/non-const way of accessing the elements (the iterator).


Yes but generally you don't expect iterators to own objects.
Anyways, my array class isn't supposed to work as a containter but rather like a RAII replacement for T *. The same goes or my ptr class.

Share this post


Link to post
Share on other sites
ToohrVyk    1595
Quote:
Original post by rozz666
Yes but generally you don't expect iterators to own objects.
Anyways, my array class isn't supposed to work as a containter but rather like a RAII replacement for T *. The same goes or my ptr class.


My point is that you use the array to own the objects, and then manipulate it through iterators (so that you never need to convert an array of non-const to an array of const).

Share this post


Link to post
Share on other sites
rozz666    896
Quote:
Original post by ToohrVyk
Quote:
Original post by rozz666
Yes but generally you don't expect iterators to own objects.
Anyways, my array class isn't supposed to work as a containter but rather like a RAII replacement for T *. The same goes or my ptr class.


My point is that you use the array to own the objects, and then manipulate it through iterators (so that you never need to convert an array of non-const to an array of const).


I understand, but my point is that sometimes you need to manipulate the arrays not just iterators. You need to transfer ownership, etc. Do you suggest that there's no need for any array<const T>?
Imagine a string class and imagine you have "some text". Now, you want to use "some text" as a string object. string internally has array<char>. You'll have to create a copy of "some text" and store it the array. However, when you have array<const char> inside the string you can "cast" "some text" to array<const char> making an object with ref_cnt = NULL. And therefore you can "cast" "some tect" into a string object without making a copy.

Share this post


Link to post
Share on other sites
Antheus    2409
The fact that compiler is giving you trouble, and that you need to use reinterpret_cast to avoid it is a good hint that it's not a good idea.

Const is optional. It doesn't really do much for the code itself, but it's a strong reminder to users of the code that something either may or may not be modified.

Yes, you can get around the limitation like this, but it's very easy to miss something that will cause problems down the line.

I encountered const-correctness problems in component model. While I designed lower levels rigidly with const members in mind, a subtle flaw emerged 2 layers away. It took me quite a while before I could come up with scenario that would cause problems if I violated constness, and it turned out it wouldn't be that hard to encounter that in real code.

So the fact you encountered problems with this conversion is a good thing. It's pointing out that you should do something. Even if "some stuff" may seem irrelevant here and now, you are deliberately ignoring something important your compiler is telling you.

Share this post


Link to post
Share on other sites
rozz666    896
It seems the solution with deriving classes works!

Here's my code:


template <typename T>
class array;

template<typename T>
class array<const T> {
public:

array();

array(size_t size, const T *v);

array(const array<const T>& rhs);

protected:

array(size_t size, const T *v, uint *num_refs);

public:

static array<const T> cast(const T *v, size_t size);

~array();

array<const T>& operator=(const array<const T>& rhs);

const T& operator[](int idx) const;

size_t get_byte_size() const;

array<T> copy() const;

const T *raw() const;

public:

const size_t size;

protected:

const T *data;
uint *num_refs;
};

template<typename T>
class array : public array<const T> {
public:

array();

explicit array(size_t size);

array(size_t size, const T *v);

array(const array<T>& rhs);

private:

array(size_t size, T *v, uint *num_refs);

public:

static array<T> cast(T *v, size_t size);

T& operator[](int idx) const;

T *raw() const;
};

template <typename T>
inline array<T>::array() : array<const T>() { };

template <typename T>
inline array<T>::array(size_t size) : array<const T>(size, new T[size], new uint(1)) { };

template <typename T>
inline array<T>::array(size_t size, const T *v) : array<const T>(size, v) { };

template <typename T>
inline array<T>::array(const array<T>& rhs) : array<const T>(rhs) { };

template <typename T>
inline array<T>::array(size_t size, T *v, uint *num_refs) : array<const T>(size, v, num_refs) { };

template <typename T>
inline static array<T> array<T>::cast(T *v, size_t size)
{
return array<T>(size, v, NULL);
};

template <typename T>
inline T& array<T>::operator[](int idx) const
{
conditional::throw_if_array_out_of_bounds<options::throw_array_out_of_bounds>::perform(*this, idx);

return const_cast<T& >(data[idx]);
};

template <typename T>
inline T *array<T>::raw() const
{
return const_cast<T *>(data);
};


template <typename T>
inline array<const T>::array() : data(NULL), size(0), num_refs(NULL) { };

template <typename T>
inline array<const T>::array(size_t size, const T *v)
: size(size), data(new T[size]), num_refs(new uint(1))
{
copy(data, v, size);
};

template <typename T>
inline array<const T>::array(const array<const T>& rhs)
: data(rhs.data), size(rhs.size), num_refs(rhs.num_refs)
{
if (num_refs) ++*num_refs;
};

template <typename T>
inline array<const T>::array(size_t size, const T *v, uint *num_refs)
: data(v), size(size), num_refs(num_refs) { };

template <typename T>
inline array<const T> array<const T>::cast(const T *v, size_t size)
{
return array<const T>(size, v, NULL);
};

template <typename T>
inline array<const T>::~array()
{
if (num_refs && !--*num_refs) {

delete[] data;
delete num_refs;
}
};

template <typename T>
inline array<const T>& array<const T>::operator=(const array<const T>& rhs)
{
if (rhs.num_refs) ++*rhs.num_refs;

if (num_refs && !--*num_refs) {

delete[] data;
delete num_refs;
}

data = rhs.data;
const_cast<uint& >(size) = rhs.size;
num_refs = rhs.num_refs;

return *this;
};

template <typename T>
inline const T& array<const T>::operator[](int idx) const
{
conditional::throw_if_array_out_of_bounds<options::throw_array_out_of_bounds>::perform(*this, idx);

return data[idx];
};

template <typename T>
inline uint array<const T>::get_byte_size() const
{
return size * sizeof(T);
};

template <typename T>
inline array<T> array<const T>::copy() const
{
array<T> b(size);

copy(b.data, data, size);

return b;
};

template <typename T>
inline const T *array<const T>::raw() const
{
return data;
};







array<int> a1;
array<const int> a2;

a1 = a2; // wrong, doesn't compile
a2 = a1; // ok

void f1(array<int>& );
void f2(array<const int>& );

f1(a1); // ok
f1(a2); // wrong, doesn't compile
f2(a1); // ok
f2(a2); // ok



No temporary objects are created.

What do you think about it?

[Edited by - rozz666 on October 13, 2007 2:09:13 PM]

Share this post


Link to post
Share on other sites
rozz666    896
Quote:
Original post by Antheus
The fact that compiler is giving you trouble, and that you need to use reinterpret_cast to avoid it is a good hint that it's not a good idea.

Const is optional. It doesn't really do much for the code itself, but it's a strong reminder to users of the code that something either may or may not be modified.

Yes, you can get around the limitation like this, but it's very easy to miss something that will cause problems down the line.

I encountered const-correctness problems in component model. While I designed lower levels rigidly with const members in mind, a subtle flaw emerged 2 layers away. It took me quite a while before I could come up with scenario that would cause problems if I violated constness, and it turned out it wouldn't be that hard to encounter that in real code.

So the fact you encountered problems with this conversion is a good thing. It's pointing out that you should do something. Even if "some stuff" may seem irrelevant here and now, you are deliberately ignoring something important your compiler is telling you.



There are no reinterpret_cast's. Just const_cast, and it's not incorrect to use them. When you derive from class you create a new class with more capabilities. E.g. elements of array<const int> can only be read. Now you derive from it, making array<int> and you give it's elements ability to be modified. Now const is the opposite. An int can be modified and read, but by adding const you add a limitation. That's why you have to make a lot of const_cast's in array<int> - because you have to add more functionality and there is no type modifier that removes constness.

Share this post


Link to post
Share on other sites
Extrarius    1412
Quote:
Original post by rozz666
[...]I understand, but my point is that sometimes you need to manipulate the arrays not just iterators. You need to transfer ownership, etc. Do you suggest that there's no need for any array<const T>?[...]
I don't understand why you ever need 'array<const T>'

Any situation I can think of where that would make sense, 'const array<T>' makes just as much if not more sense. The approach works perfectly for vector, and it really really makes sense to keep the object type the same if you're going to be tracking references. Changing the object's type will make the code very very confusing, and changing template parameters is doing exactly that - changing the object's type.

Share this post


Link to post
Share on other sites
rozz666    896
Quote:
Original post by Extrarius
Quote:
Original post by rozz666
[...]I understand, but my point is that sometimes you need to manipulate the arrays not just iterators. You need to transfer ownership, etc. Do you suggest that there's no need for any array<const T>?[...]
I don't understand why you ever need 'array<const T>'

Any situation I can think of where that would make sense, 'const array<T>' makes just as much if not more sense. The approach works perfectly for vector, and it really really makes sense to keep the object type the same if you're going to be tracking references. Changing the object's type will make the code very very confusing, and changing template parameters is doing exactly that - changing the object's type.


vector<T> is a container, where array<T> is a "pointer". When you write a1 = a2 you don't copy the content, just the reference. array<T> is intended to work almost exactly like T *, so you need array<const T> everywhere you need const T *.
And by deriving, every array<T> is also array<const T>.

Share this post


Link to post
Share on other sites
Extrarius    1412
How not? a const shared_array<T> will only give you a const pointer, so you won't be able to modify the data without using a const cast, and you can easily convert from a non-const shared_array<T> to a const shared_array<T> (and it will be done implicitly).

If you insist on doing it yourself, make ref_cnt mutable (it's not part of the visible state, after all) and then just use a const array<T> where the data shouldn't be changed. Resize on an array of constants doesn't make sense (except to shrink it, perhaps, but that would be an odd situation) so a const array should work everywhere you don't want the contents to be changed.

Share this post


Link to post
Share on other sites
rozz666    896
Quote:
Original post by Extrarius
How not? a const shared_array<T> will only give you a const pointer, so you won't be able to modify the data without using a const cast, and you can easily convert from a non-const shared_array<T> to a const shared_array<T> (and it will be done implicitly).


Is the opposite is also true?

const shared_array<int> a1;
shared_array<int> a2;

a2 = a1;


Quote:


If you insist on doing it yourself, make ref_cnt mutable (it's not part of the visible state, after all) and then just use a const array<T> where the data shouldn't be changed. Resize on an array of constants doesn't make sense (except to shrink it, perhaps, but that would be an odd situation) so a const array should work everywhere you don't want the contents to be changed.


But it won't work. const array<int> means a constant array of integer, which means a constant pointer. array<const int> means an array of constant integers. It means you can modify/copy the array objects, you just can't modify the integers. The same difference as int *const and const int *.

This would work if the = operator copied the content, but it doesn't. It copies the pointer, so const array<T> is just T *const.

Share this post


Link to post
Share on other sites
rozz666    896
I thought I found a flaw :-)

consider:

void swap(array<const T>& , array<const T>& );

array<int> a1;
array<const int> a2;

swap(a1, a2); // works!


but it's "normal":



void swap(const int *& a1, const int *& a2);

int x = 2, *p1 = &x;
const int y = 3, *p2 = &y;

swap(p1, p2); // also works


So in both cases you will be able to modify a const object without const_cast.

Share this post


Link to post
Share on other sites
johdex    247
It's not safe. Someone may later provide specialized versions of your class templates that use different memory layouts between the const and non-const versions. Your cast() function will then break silently.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this