How to redistribute in C ++?

The following code is an MCVE , this reproduces the problem I want to ask about, but it is not real code. The actual code is quite complex, so I wrote this to demonstrate the problem.

The key feature I'm looking for is the ability to create a dynamically allocated array, please don't suggest using stl

it because that is clearly prohibited. This code is for educational purposes and therefore there are limitations.

#include <cstring>
#include <iostream>

class Value
{
    public:
        Value(int value = 0);
        Value(const Value &value);
        Value &operator =(const Value &other);
        ~Value();

        operator int() {return *m_absurdPointer;}

    private:
        int *m_absurdPointer;
};

Value::Value(int value) :
    m_absurdPointer(new int[1])
{
    *m_absurdPointer = value;
}

Value::Value(const Value &value)
{
    m_absurdPointer = new int[1];
    memcpy(m_absurdPointer, value.m_absurdPointer, sizeof(*m_absurdPointer));
}

Value &Value::operator =(const Value &other)
{
    m_absurdPointer = new int[1];
    memcpy(m_absurdPointer, other.m_absurdPointer, sizeof(*m_absurdPointer));

    return *this;
}

Value::~Value()
{
    delete[] m_absurdPointer;
}

class ValueArray
{
    public:
        ValueArray();
        ~ValueArray();

        void append(const Value &value);
        void show() const;

    private:
        Value *m_array;
        unsigned int m_capacity;
        unsigned int m_length;
};


ValueArray::ValueArray() :
    m_array(nullptr)
  , m_capacity(0)
  , m_length(0)
{
}

ValueArray::~ValueArray()
{
    delete[] m_array;
}

void
ValueArray::append(const Value &value)
{
    if (m_length >= m_capacity)
    {
        Value *newarray;
        unsigned int unitSize;

        unitSize = 1;       
        newarray = new Value[m_capacity + unitSize];

        if ((m_capacity > 0) && (m_array != nullptr))
            memcpy(newarray, m_array, m_capacity * sizeof(*m_array));
        delete[] m_array;

        m_array = newarray;
        m_capacity += unitSize;
    }
    m_array[m_length++] = value;
}

void
ValueArray::show() const
{
    for (size_t i = 0 ; i < m_length ; ++i)
        std::cout << static_cast<int>(m_array[i]) << std::endl;
}

int
main(void)
{
    ValueArray example;

    for (int i = 0 ; i < 10 ; ++i)
        example.append(Value(i));
    example.show();

    return 0;
}

      

This raises a double problem free

because it delete[] m_array;

calls the destructor of the class Value

after it has copied the values ​​into the rec new

ed array .

I tried to do it with malloc()

/ realloc()

, but I need the destructor to Value()

be called so new

is required because I cannot use free()

.

How do I prevent this? If I delete delete[] m_absurdPointer;

then the double free is gone, of course, but a memory leak occurs.

+3


source to share


2 answers


Basically you want to implement your own vector class, right?

OK, first things first: as far as I know, you cannot increase the previously allocated memory. At least not with a standard valve.

So, you need to allocate a new, larger amount of memory.

You can do it the standard way using the new one:

Type * newdata = new Type[size];

      

In this case, for each new element Type

, the class constructor will be called , which is size

times.

To get your old data into this new array, you need to copy or move it there:

for (size_t it = 0; it < oldsize; ++it) {
  newdata[it] = olddata[it];
  // newdata[it] = std::move(olddata[it]);
}

      



This is what is std::copy

accordingly. std::move

do. (You can also use it std::swap

inside a loop.)

To do this, the class Type

needs both a default constructor and a valid copy or move implementation.

You are using memcpy

. In C ++, this is generally a bad idea: your implemented assignment operator is not called, so both objects in your old array and raw copies use the same pointer, so you get that double free, obviously.

You can also allocate raw memory and use the new location to copy or move new objects from old ones:

void * memory = new char[size * sizeof(Type)];
for (size_t it = 0; it < oldsize; ++it) {
  new (memory + it * sizeof(Type)) Type(olddata[it]); // copy
}

      

The above example is just an example, for real code you need to consider alignment as well.

Finally, I'm pretty sure you can somehow trick the default distribution to free up your (old) memory without destroying the objects inside, thus allowing the original copy to be used memcpy

. While this would be a hack and might break on complex classes, this is not the C ++ way of doing it.

The idiomatic way is to copy or move old objects to a new repository (with a purpose or design).

+2


source


You should use the move constructor if you need to stick with a vector implementation ValueArray

:



class Value
{
    public:
        Value(int value = 0);
        Value(const Value &value);
        Value(Value&& val);
        Value &operator =(const Value &other);
        Value &operator =(Value&& other);
        ~Value();

        operator int() {return *m_absurdPointer;}

    private:
        int *m_absurdPointer;
};

Value::Value(Value&& o) : m_absurdPointer(o.m_absurdPointer) {
  o.m_absurdPointer = nullptr;
}
Value &operator =(Value&& o) {
  delete[] this->m_absurdPointer;
  this->m_absurdPointer = o.m_absurdPointer;
  o.m_absurdPointer = nullptr;
}

void
ValueArray::append(const Value &value)
{
if (m_length >= m_capacity)
{
    Value *newarray;
    unsigned int unitSize;

    unitSize = 1;       
    newarray = new Value[m_capacity + unitSize];

    if ((m_capacity > 0) && (m_array != nullptr)) {
      std::move(m_array, m_array + m_length, newarray);
    }
    delete[] m_array;

    m_array = newarray;
    m_capacity += unitSize;
}
}

      

0


source







All Articles