tags:

views:

251

answers:

8
+2  Q: 

const and pointers

Edit1: I realize this is hard to understand this question without having an insight of what I'm trying to do. The class A is not complete but it essentially stand for a C-array "proxy" (or "viewer" or "sampler"). One interesting usage is too present a C-array as a 2d grid (the relevant function are not shown here). The property of this class are the following:

  • it should not own the data - no deep copyy
  • it should be copyable/assignable
  • it should be lightweight (
  • it should preserve constness (I'm having trouble with this one)

Please do not question the purpose or the design: they are the hypothesis of the question.

First some code:

class A
{
private:
    float* m_pt;
public:
    A(float* pt)
        :m_pt(pt)
    {}
    const float* get() const
    {
        return m_pt;
    }
    void set(float pt)
    {
        *m_pt = pt;
    }
};

void gfi()
{
    float value = 1.0f;
    const A ac(&value);
    std::cout<<(*ac.get())<<std::endl;
    A a = ac;
    a.set(2.0f);
    std::cout<<(*ac.get())<<std::endl;
}

Calling "gfi" generate the following output:

1
2

Assigning a with ac is a cheap way to shortcut the constness of ac. Is there a better way to protect the value which m_pt point at?

Note that I DO want my class to be copyable/assignable, I just don't want it to loose its constness in the process.

Edit0: I also DO want to have a pointer in there, and no deep copy please (let say the pointer can be a gigantic array).

Edit2: thanks to the answers, I came to the conclusion that a "const constructor" would be a useful thing to have (at least in this context). I looked it up and of course I'm not the same one who reached this conclusion. Here's an interesting discussion: http://www.rhinocerus.net/forum/language-c-moderated/569757-const-constructor.html

Edit3: Finally got something which I'm happy with. Thanks for your help. Further feedback is more than welcome

template<typename T>
class proxy
{
public:
    typedef T elem_t;
    typedef typename boost::remove_const<T>::type elem_unconst_t;
    typedef typename boost::add_const<T>::type elem_const_t;
public:
    elem_t* m_data;
public:
    proxy(elem_t* data = 0)
        :m_data(data)
    {}
    operator proxy<elem_const_t>()
    {
        return proxy<elem_const_t>(m_data);
    }
}; // end of class proxy

void test()
{
    int i = 3;
    proxy<int> a(&i);
    proxy<int> b(&i);
    proxy<const int> ac(&i);
    proxy<const int> bc(&i);
    proxy<const int> cc = a;
    a=b;
    ac=bc;
    ac=a;
    //a=ac; // error C2679: binary '=' : no operator found which takes a right-hand operand of type...
    //ac.m_data[0]=2; // error C3892: 'ac' : you cannot assign to a variable that is const
    a.m_data[0]=2;
}
+2  A: 

Your class is badly designed:

  • it should use float values, not pointers
  • if you want to use pointers, you probably need to allocate them dynamically
  • and then you need to give your class a copy constructor and assignment operator (and a destructor) , which will solve the problem

Alternatively, you should prevent copying and assignment by making the copy constructor and assignment op private and then not implementing them.

anon
How can you say that a class is badly design when you don't know what it is going to be used for?
It can't be used for the use case you put forward. Therefore it is badly designed.
anon
A: 

Why not replace float* with float in A. If you don't either the original owner of the float that the float* references can change it, or anyone prepared to do a mutable cast on the return value from a::get.

Alex Brown
That would be an answer but I forgot to say that I actually need the pointer to be a pointer (see edit in the question)
A: 

const is always just a hint to the compiler; there are no ways to make a variable permanently read-only.

Ignacio Vazquez-Abrams
A: 

I think you should use deep copy and define your own assingment operator and copy constructor.

Also to return handle to internal data structure in not a good practice.

Ashish
No deep copy would be an answer but this is not possible here (see edit)
+1  A: 

You can trick around with proxy pattern and additional run-time constness boolean member. But first, please tell us why.

Pavel Radzivilovsky
I added further explanations at the beginning of the question
A: 

EDIT: considering this question some more, I think you are misinterpreting the effect of const-correctness on member pointers. Consider the following surprising example:

//--------------------------------------------------------------------------------
class CNotSoConstPointer
 {
 float *mp_value;

 public:
   CNotSoConstPointer(float *ip_value) : mp_value(ip_value) {}

   void ModifyWithConst(float i_value) const
     {
     mp_value[0] = i_value;
     }

   float GetValue() const
     {
     return mp_value[0];
     }
 };

//--------------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])
  {
  float value = 12;
  const CNotSoConstPointer b(&value);
  std::cout << b.GetValue() << std::endl;

  b.ModifyWithConst(15);
  std::cout << b.GetValue() << std::endl;

  while(!_kbhit()) {}
  return 0;
  }

This will output 12 and then 15, without ever being "clever" about the const-correctness of the const not-so-const object. The reason is that only the pointer ITSELF is const, not the memory it points to.

If the latter is what you want, you'll need a lot more wrapping to get the behavior you want, like in my original suggestion below or Iain suggestion.

ORIGINAL ANSWER:


You could create a template for your array-proxy, specialized on const-arrays for the const version. The specialized version would have a const *m_pt, return a const pointer, throw an error when you try to set, and so on.

Edit: Something like this:

template<typename T>
class TProxy
  {
  T m_value;

  public:
    TProxy(T i_t) : m_value(i_t) {};

    template<typename T>
    TProxy(const TProxy<T> &i_rhs) : m_value(i_rhs.m_value) {}

    T get() { return m_value; }
    void set(T i_t) { m_value = i_t; }
  };

template<typename T>
class TProxy<const T *>
  {
  const T *mp_value;

  public:
    TProxy(const T *ip_t) : mp_value(ip_t) {};

    template<typename T>
    TProxy(const TProxy<T> &i_rhs) : m_value(i_rhs.mp_value) {}

    T get() { return m_value; }    
  };
MadKeithV
That will be the answer if I cannot find anything simpler.I'm ok with using const as long as it doesn't mess with my code too much. I would say your answer, from my perspective, is borderline...
about the edit, I understand and agree.But I thought I might be able to "misused" the const semantic for another purpose. Indeed, when making a function "const", I can decide to protect MORE than what the conventional way dictate.Note that the only thing missing to achieve this goal is the lack of "const constructor". Basically, it is impossible to declare a constructor as being only able to create const object only. Shame...
Actually, going that way, you could have a private constructor and a factory method that only creates const objects.
MadKeithV
Assignment and copy construcotrs are still suppose to work. I think a factory could only provide solution with pointers (no construction on the stack) and a heavy syntax. That would defeat the lightweiht (and easy to use) aspect of things.
A: 

You can deny the copy-constructor for certain combinations of arguments:

For instance, adding the constructor;

A(A& a) :m_pt(a.m_pt) { m_pt = a.m_pt; }

prevents any instance of A being initialised with a const A.

This also prevents const A a2 = a1 where a1 is const, but you should never need to do this anyway, since you can just use a1 directly - it's const even if you could make a copy, a2 would be forever identical to a1.

Alex Brown
This works.Unfortunately I also need to allow:const A a2 = a1;Indeed, a function could be returning a const class:const A getProxy() const;
+2  A: 

Effectively your class is like an iterator that can only see one value. It does not encapsulate your data just points to it.

The problem you are facing has been solved for iterators you should read some documentation on creating your own iterator and const_iterator pairs to see how to do this.

Note: in general a const iterator is an iterator that cannot be incremented/decremented but can change the value it points to. Where as a const_iterator is a different class that can be incremented/decremented but the value it points to cannot be changed.

This is the same as the difference between const float * and float *const. In your case A is the same as float * and const A is the same as float *const.

To me your choices seem to be:

  • Encapsulate your data.
  • Create a separate const_A class like iterators do
  • Create your own copy constructor that does not allow copies of const A eg with a signature of A(A & a);
iain
This answer is similar to MadKeithV's answer.I'm strongly suspect it will end up being the correct answer but I'm still hoping for a simpler one...