1

I can not get the idea of how to create Array template class properly in C++. The problem is solely out of learning purposes.

Let me provide the code first.

Array.h :

//Developed by Trofimov Yaroslav on 30.03.2018

#ifndef _ARRAY_H_TROFIMOV_
#define _ARRAY_H_TROFIMOV_

#include <string>

template<const size_t n, typename T>
class Array {
    static unsigned __freeId, __quantity;
    unsigned _id;

    T** _array;
    const size_t _n;
public:
    typedef const bool (* const BooleanResultDelegate)(const T&);
    class ArrayError {
        const std::string _reason;
        const size_t _index;
        const size_t _maxIndex;
    public:
        ArrayError(const size_t index, const size_t maxIndex,const std::string& reason = "") 
            : _index(index), _maxIndex(maxIndex), _reason(reason) {}
        std::string explanation(void) {
            std::string res += "Index: " + std::to_string(_index) + "\n"; 
            res += "Max index: " + std::to_string(_maxIndex) + "\n"; 
            res += "Reason: " + _reason + "\n"; 
            return res;
        }
    };

    explicit Array<n, T>(T* arrayFiller = 0) 
        : _n(n), _array(new T*[n]), _id(++__freeId) {
            if(arrayFiller != 0) {
                for(size_t i(0); i < length(); ++i) {
                    _array[i] = new T(*arrayFiller);
                }
            } else {
                for(size_t i(0); i < length(); ++i) {
                    _array[i] = arrayFiller;
                }
            }
            reportIfDebug<n, T>(*this, "created");
            ++__quantity;
    }
    explicit Array<n, T>(const T& arrayFiller) 
        : _n(n), _array(new T*[n]), _id(++__freeId) {
            for(size_t i(0); i < length(); ++i) {
                _array[i] = new T(arrayFiller);
            }
            reportIfDebug<n, T>(*this, "created");
            ++__quantity;
    }
    Array<n, T>(const Array<n, T>& that) 
        : _n(n), _array(new T[n]), _id(++__freeId) {
            for(size_t i(0); i < length(); ++i) {
                (*this)[i] = new T[that[i]];
            }
            reportIfDebug<n, T>(*this, "created");
            ++__quantity;
    }
    ~Array<n, T>(void) {
        removeAll();
        delete [] _array; _array = 0;
        reportIfDebug<n, T>(*this, "deleted", false);
        --__quantity;
    }

    T* operator[](const size_t i) {
        if(i > length()) {
            throw ArrayError(i, _n, "out of bounds exception");
        }
        return _array[i];
    }
    const T* operator[](const size_t i) const {
        if(i > length()) {
            throw ArrayError(i, _n, "out of bounds exception");
        }
        return _array[i];
    }
    const size_t length() const {
        return _n;
    }
    const unsigned getID() const {
        return _id;
    }

    void removeAll(BooleanResultDelegate removeCondition = 0) {
        for(size_t i(0); i < length(); ++i) {
            if(removeCondition == 0 || removeCondition(*_array[i])) {
                delete [] _array[i]; _array[i] = 0;
            }
        }
    }
};

template<const size_t n, typename T>
unsigned Array<n, T>::__freeId(0);
template<const size_t n, typename T>
unsigned Array<n, T>::__quantity(0);

template<const size_t n, typename T>
void reportIfDebug(
    const Array<n, T>& instance, 
    const char* const message, 
    const bool showContent = true) {
#ifndef NDEBUG
    std::cout << "========================================" << std::endl;
    std::cout << typeid(instance).name() << ' ' 
        << message << ' '
        << "id: " << instance.getID() << std::endl;
    if(showContent) {
        std::cout << instance;
    }
    std::cout << "========================================" << std::endl;
#endif
}
template<const size_t n, typename T>
std::ostream& operator<<(std::ostream& os, const Array<n, T>& instance) {
    for(size_t i(0); i < instance.length(); ++i) {
        if(instance[i] == 0) {
            os << "[" << i << "]: " << instance[i] << "\n";
        } else {
            os << "[" << i << "]: " << *instance[i] << "\n";
        }
    }
    return os;
}
#endif

Main.cpp :

//Developed by Trofimov Yaroslav on 30.03.2018
#include <iostream>
#include "Array.h"

int main(void) {
    const Array<5, int> a(7);
    std::cout << *a[2] << std::endl;
    return 0;
}

What is the main problem right now - is that the client of my Array class would have to use [indirection operator *] and [0 value pointer check] to use the objects from array.

I do not want the client to do that. But, if I use reference instead of pointer as a return type of operator[] I will not be able to return types which do not have copy constructor and I will return trash if nothing was put there before.

It seems like in Java and C# the problem is not fixed as well. The user is getting a reference to an object there and definitely should check for null being returned. And [indirection operator *] is called automatically there.

ggghahaha
  • 55
  • 1
  • 8
  • 1
    First thing I'd fix is the fact that this code uses "reserved identifiers". Then, I'd make sure that `ALL_UPPERCASE` names are only used for macros. Unless those formal issues are fixed, I wouldn't even start looking for further errors. – Ulrich Eckhardt Mar 31 '18 at 11:10
  • @UlrichEckhardt, what `reserved identifiers` do you see in my code? I do not see any. Point them out to me, please. – ggghahaha Mar 31 '18 at 11:12
  • No I wont. How about you research that meaning first and then it should be obvious to you, too. – Ulrich Eckhardt Mar 31 '18 at 11:13
  • @UlrichEckhardt, do you mean double underscore? – ggghahaha Mar 31 '18 at 11:15
  • 1
    @ggghahaha a few examples of reserved identifiers that you use: `_ARRAY_H_TROFIMOV_`, `__freeId`. – eerorika Mar 31 '18 at 12:03
  • @user2079303, but at the same time as far as I know it is preferably to use underscores and uppercase for guardians. And I see no harm in using double underscore in my class namespace. – ggghahaha Mar 31 '18 at 12:06
  • @ggghahaha It's OK to use underscores and uppercase for guardian macros. It's just not OK to use reserved names. If you see no harm in your program having undefined behaviour, then I feel sorry for your (potential) co-workers. – eerorika Mar 31 '18 at 12:15
  • @ggghahaha *I will not be able to return types which do not have copy constructor* -- All types, unless explicitly turned off, have copy constructors or are copyable. So what is your concern? And why not simply do what the C++ standard classes do? Just return a reference for `operator []`. – PaulMcKenzie Mar 31 '18 at 12:16
  • @user2079303, what undefined behavior are you talking about? – ggghahaha Mar 31 '18 at 12:22
  • 1
    @ggghahaha -- As far as undefined behavior, you have no user-defined assignment operator to go along with the copy constructor and destructor. Second, the creation of the array itself is naive -- [something like this](https://stackoverflow.com/questions/21943621/how-to-create-a-contiguous-2d-array-in-c/21944048#21944048) uses less calls to the allocator. Third, you should [read this concerning underscores in names](https://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) – PaulMcKenzie Mar 31 '18 at 12:25
  • 1
    @ggghahaha This: *"If a program declares or defines a name in a context where it is reserved, other than as explicitly allowed by this Clause, its behavior is undefined."* (quote from the standard) – eerorika Mar 31 '18 at 12:25
  • @user2079303, why do you think that `_ARRAY_H_TROFIMOV_` is reserved? – ggghahaha Mar 31 '18 at 12:27
  • 2
    @ggghahaha *"Each name that contains a double underscore _ _ or begins with an underscore followed by an uppercase letter (2.12) is reserved to the implementation for any use."* (quote from the standard) – eerorika Mar 31 '18 at 12:29
  • 1
    SO answer about "reserved identifiers" https://stackoverflow.com/a/228797/3871028 – Ripi2 Mar 31 '18 at 12:39
  • 1
    @ggg - One thing to consider is that none of the existing classes `std::array` and `std::vector` store a `T**`. Perhaps this is where your problems start? – Bo Persson Mar 31 '18 at 13:16
  • @BoPersson, but how else could I store an array of pointers? – ggghahaha Mar 31 '18 at 13:47
  • 1
    @ggg - Perhaps not at all? Sorry. :-) You haven't described if this class is supposed to do anything in addition to what the standard classes do , but you might want to consider that `std::array` stores something like `T _array[n]`. No pointers. – Bo Persson Mar 31 '18 at 13:56

0 Answers0