1

I have a class that looks somewhat like this:

class S
{
public:
    int* data;
    S() : data(new int[10]) {}
};

The constructor allocates the memory of 10 integers, and the default copy constructor as expected merely copies the pointer itself rather than the content.

Even if there is an instance of S that has const modifier, I can modify the data that data points to, since that data itself does not have const modifier. I could avoid this by making data private and only allowing write access via a non-const method like so:

class S
{
private:
    int* data;
public:
    S() : data(new int[10]) {}

    int& operator(size_t i)
    {
        return data[i];
    }

    const int& operator(size_t i) const
    {
        return data[i];
    }
};

But now I can use the copy constructor to circumvent the constness of the instance of S like so:

void main()
{
    const S a; // Allocates memory
    S b(a); // Also points to memory allocated for a
    b(1) = 3; // Writes to a even though it is not supposed to be mutable
}

What would be an elegant way to solve this problem (potentially using templates)?

  • The data pointed to by an instance of const S should not be mutable at all using only this instance.
  • Copy constructor should only copy pointer, but not make a deep copy of the data.
  • Both a const S and an S should be creatable via a copy constructor given an instance of S such that the const instance cannot modify the data, but the non-const instance can.

2 Answers2

1

It is possible to know in the copy constructor if the object being copied is const by providing two different copy constructors, one which takes a const parameter and one which does not. The compiler will select whichever version matches the passed parameter. Set a flag in the constructor so it can throw an error when a non-const operation is performed.

The best way to avoid the leaked memory shown in the question is to used a smart pointer like std::shared_ptr rather than a raw pointer. Unfortunately shared_ptr is meant for single objects, not arrays; workarounds are possible as in this StackOverflow question. I'm not going to try to solve this now, the code below still has the leak.

To be complete you should follow the Rule of Three and provide an operator= and destructor as well. I left this as an exercise for the reader.

class S
{
private:
    int* data;
    bool is_const;
public:
    S() : data(new int[10]), is_const(false) { data[1] = 42; }
    S(const S& other) : data(other.data), is_const(true) {}
    S(S& other) : data(other.data), is_const(false) {}

    int& operator()(size_t i)
    {
        if (is_const)
            throw std::logic_error("non-const operation attempted");
        return data.ptr[i];
    }

    const int& operator()(size_t i) const
    {
        return data.ptr[i];
    }
};

See it in action: http://ideone.com/SFN89M

Mark Ransom
  • 271,357
  • 39
  • 345
  • 578
0

Delete the copy constructor (and assignment operator) for S. Create a new proxy class (SCopy) that holds a pointer to an S object (which is passed in to the constructor for SCopy). SCopy would then implement the const int &operator() const and not the non-const version.

This would then allow you to implement a destructor in S that would free the memory you're currently leaking.

1201ProgramAlarm
  • 30,320
  • 7
  • 40
  • 49
  • I forgot to mention that in my post, but I want to be able to create both "reference instances" that are able to and not able (`const`) modify the referenced data, preferably such that I can differentiate between the two merely via a `const` keyword. – John Mescard May 24 '17 at 16:09
  • If you want the `const`-ness to do too much, it becomes easier to have both an `S` and a `ConstS` class (compare: `iterator` vs `const_iterator` for containers). – Daniel H May 25 '17 at 22:21