-5
\$\begingroup\$

I have written some improved C-type classes in C++ and wondered whether these are suitable for professional use or in which way they would have to be improved to be professional or more powerful.

Especially in such cases I was not sure about it:

template <typename arg, typename ...args>
void DoubleArray::append(arg number, args ...arguments)
{
    double *temp = new double[this->size];
    for (int i = 0; i < this->size; i++)
    {
        temp[i] = this->data[i];
    }

#ifdef FLOAT_ARRAY_ALLOC_MAX
    if (FLOAT_ARRAY_ALLOC_MAX > this->size)
    {
        this->size++;
        this->data = new double[this->size];
        for (int i = 0; i < (this->size - 1); i++)
        {
            this->data[i] = temp[i];
        }
        this->data[this->size - 1] = number;
    }
#else
    this->size++;
    this->data = new double[this->size];
    for (int i = 0; i < (this->size - 1); i++)
    {
        this->data[i] = temp[i];
    }
    this->data[this->size - 1] = number;
#endif // FLOAT_ARRAY_ALLOC_MAX

    temp = new double;
    delete temp;
    temp = NULL;

#ifdef FLOAT_ARRAY_ALLOC_MAX
    if (FLOAT_ARRAY_ALLOC_MAX > this->size)
    {
        append(arguments...);
    }
#else
    append(arguments...);
#endif // FLOAT_ARRAY_ALLOC_MAX
}

So far the library I created consists of improved c-types that allow you to manage RAM more easily. Moreover, it includes array-classes which provide methods like append or insert to simplify the work with pointers.

This repo/lib could help others to program code more quickly, in which pointers would be used often. I would like to create a replacement for the C++ standard library.

\$\endgroup\$
15
  • 2
    \$\begingroup\$ This question is incomplete. To help reviewers give you better answers, please add sufficient context to your question. The more you tell us about what your code does and what the purpose of doing that is, the easier it will be for reviewers to help you. Questions should include a description of what the code does \$\endgroup\$ Commented Jun 17, 2016 at 12:44
  • 1
    \$\begingroup\$ @JnxF I kindly hate that too but nowadays any half-decent compiler will optimize it. Still I can't stop to feel it annoying... \$\endgroup\$ Commented Jun 17, 2016 at 13:10
  • 4
    \$\begingroup\$ No need to apologize, I don't think you're misleading me. I think I understood what you were trying to say. What I was trying to say, and I apologize for not saying it well, is that you won't likely simplify C++ and shorten the development of C++ programs. C++ just simply is complex. You can choose not to use the complexities and subtleties of it, but if you do so, you typically end up with more bloated code (and probably more bugs). The C++ library is big and complex precisely so coders can write shorter, simpler code. \$\endgroup\$ Commented Jun 17, 2016 at 13:33
  • 1
    \$\begingroup\$ Good luck, Max. As part of your learning process, I think you may want to look into the concept of Smart Pointers in C++. They provide much of what you were trying to add in terms of 'worry-free' memory management. \$\endgroup\$ Commented Jun 17, 2016 at 15:03
  • 2
    \$\begingroup\$ @scottbb maybe you should convert your comments into an answer, sincere they seem to have been useful. \$\endgroup\$ Commented Jun 17, 2016 at 15:22

1 Answer 1

6
\$\begingroup\$

Not much to comment on (I initially though).

So far the library I created consists of improved c-types that allow you to manage RAM more easily.

Unlikely. In C++ memory is managed by containers and smart pointers. You are not going to get better than the existing libraries (even professionals are going to have a hard time).

Moreover it includes array-classes which provide methods like 'append' or 'insert' to simplify the work with pointers.

These are already supported by the standard containers. See std::vector::insert it does exactly what you want (though you could wrap it in a nicer interface).

This repo/lib shall people help to program code more quickly, in which pointers would be used often.

C++ you don't use pointers directly very much (creators of class should hide any pointer so that users of the class don't need to use them). So you are trying to solve a problem that does not exist.

I would like to create a replacement for the C++ standard library.

That is basically a ridicules statement. Nobody is going to trust something that attempts to replaces the standard library (not even boost went in that direction). Now you could provide helpers that augment and make the standard library easier to use. But you are going to have to provide interfaces that are familiar to people that already use the standard. Replacing it with a python like version of the standard library is just a non starter.

Code Review

Conditional Code

Writing conditional macros like this is very non standard.

#ifdef FLOAT_ARRAY_ALLOC_MAX
    if (FLOAT_ARRAY_ALLOC_MAX > this->size)
    {
        this->size++;
        this->data = new double[this->size];
        for (int i = 0; i < (this->size - 1); i++)
        {
            this->data[i] = temp[i];
        }
        this->data[this->size - 1] = number;
    }
#else
    this->size++;
    this->data = new double[this->size];
    for (int i = 0; i < (this->size - 1); i++)
    {
        this->data[i] = temp[i];
    }
    this->data[this->size - 1] = number;
#endif // FLOAT_ARRAY_ALLOC_MAX

You should not have chunks of code conditionally replaced. Usually you put each section of code in a function. Then conditionally define the function. But your code is even stranger than that. You basically only add a test if some variable is defined. I would change the code to always do the test.

#ifdef FLOAT_ARRAY_ALLOC_MAX
bool rangeCheckValid(DoubleArray const& da) {return FLOAT_ARRAY_ALLOC_MAX > da.size;}
#else
// Because this is a `contexpr` the compiler can see that
// it can determine the result at compile time. It will thus see
// that the actual test is not required and probably not even plant
// the conditional  test.
constexpr bool rangeCheckValid(DoubleArray const&) {return true}
#endif

    if (rangeCheckValid(*this))
    {
        this->size++;
        this->data = new double[this->size];
        for (int i = 0; i < (this->size - 1); i++)
        {
            this->data[i] = temp[i];
        }
        this->data[this->size - 1] = number;
    }

Don't use this everywhere

Using this is a symptom of bad code where you have shadowed variables. Having shadowed variables is a really bad design as somebody will eventually make a mistake and leave of the this-> and your code will no longer work. Rather turn on your compiler warnings (and treat all warnings as errors) then your code will be less error prone.

It also makes your code much more readable.

this->data[this->size - 1] = number;

// Becomes
data[size - 1] = number;

Stop using new and raw pointers

Raw pointers and new are a blight. They are not exception safe (they will leak if there is an exception and you don't manually do memory management). You need to use some form of smart pointer to hold and delete your memory.

Also if you don't get it correct you will leak memory (Which you do)!!

    double *temp = new double[this->size];  // Allocate/

    // STUFF

    temp = new double;  // leak the previously allocated memory.
    delete temp;        // delete the new memory you just allocated.
    temp = NULL;        // Assign to NULL for some reason. This is 
                        // Bad unto itself as it hides errors when
                        // debugging the code.

What you actually wanted to do was:

    std::unique<double[]> temp(new double[this->size]);
    // STUFF

That's it memory management automatically handled in an exception safe mechanism.

Exception guarantees

In C++ we have this concept of exception guarantees. The guarantee you should strive for is the "STRONG" exception guarantee (also known as the transactional guarantee). This basically means the operation works OR (the operation fails with an exception AND the object is unchanged).

The best way to achieve this is to use the following pattern.

 1) Create temporaries that will contain the new state.
 2) Swap the old state and the new state (in an exception safe way)
 3) Release the old state.

If I re-write your code to follow that pattern

    // Create New State
    int     newSize = size + 1;
    double *newData = new double[newSize];
    for (int i = 0; i < size; i++)
    {
        // You could just use std::copy() for this.
        newData[i] = data[i];
    }
    newData[size] = number;

    // Swap the old and new State.
    std::swap(newData, data)
    std::swap(newSize, size)

    // Release the old state
    delete [] newData;

    // Recursively add new arguments.
    append(arguments...);

But even this is bad. We have used new and Raw pointers. If the data that is held is not POD type then copying it could lead to an exception and thus a leaked pointer. So we use some smart pointers. to tidy that up.

    {
        // Create New State
        int     newSize = size + 1;
        // Using smart pointer.
        std::unique<double[]> newData(new double[newSize]);
        for (int i = 0; i < size; i++)
        {
            newData[i] = data[i];
        }
        newData[size] = number;

        // Swap the old and new State.
        std::swap(newData, data)
        std::swap(newSize, size)

        // Release the old state
        // Don't need to do anything.
        // `newData` is a smart pointer and will deallocate
        // the associated memory in the destructor when it
        // goes out of scope. So it works even if an exception
        // is thrown so memory is never leaked. 
    }
    // Recursively add new arguments.
    append(arguments...);

Pre-Allocating

Your way of appending is very inefficient. Each number that you add forces a call to re-allocate memory. If you pre-allocate memory (that is not used immediately) then you can use that up over several items and don't need to re-allocate each time. This means you need to keep another member in your structure (capacity) to remember how much unused space you have.

I wrote an article about re-sizeing

Template recursion.

This is very inefficient way (though the obvious one) to use variable argument templates. I would rather use a loop to capture all the inserts. And do a single resize (if you run out of capacity).

template <typename... Args>
void DoubleArray::append(Args... arguments)
{
      append(std::initializer_list<double>(arguments...));
}
void DoubleArray::append(std::initializer_list<double> const& arguments)
{
      increaseCapacityTo(size + arguments.size());
      for(auto const& item: arguments)
      {
          // This is probably a private member
          // You can call it when you have already guaranteed there is
          // enough space in your array (see call to increaseCapacityTo())
          appendNoResize(item);
      }
}

PS: Some reading (my Blog) on how to write container like objects and do the memory management correctly.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.