Question

I'm trying to understand better how does const-correctness work and more specifically, when dealing with classes whose members are based on containers and smart pointers. I guess that the const-correctness property is the same regardless of the class members. However, since I'm having some difficulties to clearly understand what's going on, I decided to ask you for advice.

So, here is the context. I've a ShapeContainer class that has as private class member a vector of smart pointers. The Shape class is abstract and has the following virtual function virtual float doSomething(); which is then redefined by its derived classes. Note that it's a non-const class function. The relevant part of the code is given below:

class ShapeContainer{

    public:

        typedef std::shared_ptr<Shape> ShapePtr;
        typedef std::vector<ShapePtr> ShapePtrContainer;

        // .......
        const ShapePtr & operator[]( int ) const { return m_vect[index]; }; // const version
        //  ShapePtr & operator[]( int ) { return m_vect[index]; }; // non-const version
        // .......

    private:

        ShapePtrContainer m_vect;
};

class Shape{

    public:
        // ...
        virtual float doSomething() = 0;

};

Here are my questions.

Q1. Why do I'm allowed to call the doSomething() function in the following way: int index = 0; float tmp = container1[index]->doSomething(); (having ShapeContainer container1=createBasicShapes();)?
From what I understand, after calling to the const ShapePtr operator[] const function we'll get a const pointer to a Shape object, however the doSomething() virtual function is not const. So, how does a reference to a const-object can call a non-const function?

Q2. By calling the doSomething() function as previouly ilustrated (float tmp =container1[index]->doSomething();) and by adding a non-const version of the operator[], this latter overloaded version is then called instead of the const-version one. Why does it is so?

Now, instead of having a ShapeContainer class, I've now a new class named ShapeContainerInfo that still has a vector but of an intermediate ShapeInfo class (that has a smart pointer as a class member).

class ShapeContainerInfo{

    public:

        typedef std::vector<ShapeInfo> ShapeContainer;
        const ShapeInfo & operator []( int index) const { return m_vect[index]; };
        // ShapeInfo & operator []( int index) { return m_vect[index]; }; // non-const version

    private:
        ShapeContainer m_vect;
};

class ShapeInfo{

    public:

        typedef std::shared_ptr<Shape> ShapePtr;

        // ...
        float doSomething(){ return m_ShapePtr->doSomething(); };

    private:

        ShapePtr m_ShapePtr;
        int m_nID;
};

Q3. When I call float tmp = container2[i].doSomething();, I get the following compiler error: error C2662: 'ShapeInfo::doSomething' : cannot convert 'this' pointer from 'const ShapeInfo' to 'ShapeInfo &'.
However, when I add a non-const vesion of the overloaded operator [] the compiler error is gone. So, why do I really need the non-const operator[] for ShapeContainerInfo and not for ShapeContainer?

Q4. If the m_vect private member of ShapeContainerInfo is set now as public member and only the const-version of operator[] is defined (not the non-const one), there are no compiler error messages. Why this? e.g. after setting m_vect to be a public class member: float tmp = info.m_vect[i].doSomething();

Q5. How could I correctly define both the ShapeInfo and ShapeContainerInfo classes such that I only need to define the const-version of the operator[] and still being able to call the float doSomething() function?

For those of you interested in the whole sample code, please find it here.
Clarifications, suggestions are always welcomed :-) Merci!

Was it helpful?

Solution

Q1: The shared_ptr is const, that doesn't mean that the pointed to object is const. For that you would want shared_ptr<const Shape>.

Q2: Since you're ShapeContainer was not const, the non-const function was a better match, so it was called instead of the const version.

Q3: vector propagates its constness to its elements. shared_ptr does not. This is inline with the behavior of arrays and raw pointers. The elements of const arrays are const. The thing pointed to by a const pointer is not (necessarily) const.

Q4: Are you saying this produces no error?

ShapeContainerInfo info;
info[0].doSomething();

Please clarify, because that should be an error.

Q4: Okay, so you're saying that this produces no error:

ShapeContainerInfo info;
info.m_vect[0].doSomething();

Nor should it. The vector is not const. It's only inside the const member function that the vector(and all other members) are treated as const.

Q5: Make m_vect a vector of unique pointers. Inside the const function, the vector itself will be const, and the unique pointers will be const. But the objects that the unique pointers point to will be mutable.

As an example, the set function in this class is not legal:

struct Foo
{
    void set(int index, int value) const
    {
        v[index] = value;
    }

    std::vector<int> v;
};    

But this one is:

struct Foo
{
    void set(int index, int value) const
    {
        *v[index] = value;
    }

    std::vector<std::unique_ptr<int>> v;
};    
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top