1

I am trying to implement a class template for an Array class that should provide a method to count elements equal to a given element, but I get wrong results. This is my code:

main.cpp

#include <iostream>
#include "array.h"

int main() {
    int arr[] = {1, 2, 3, 2, 5, 2};
    char brr[] = {'a', 'b', 'c', 'd', 'a'};

    Array<int> A(arr, 6);
    Array<int> B(0, 7);
    Array<char> C(brr, 5);

    std::cout << A.getCount(2) << std::endl;
    std::cout << B.getCount(0) << std::endl;
    std::cout << C.getCount('a') << std::endl;

    return 0;
}

array.h

template <typename T> class Array {
    private:
    T* ptrStart;
    int size;

    public:
    Array() {
        this->ptrStart = nullptr;
        this->size = 0;
    }

    Array(T defaultValue, int size) {
        T arr[size];
        for (int i = 0; i < size; i++) arr[i] = defaultValue;
        this->size = size;
        this->ptrStart = arr;
    }

    Array(T* arr, int size) {
        this->size = size;
        this->ptrStart = arr;
    }

    int getCount(T);
};

template <typename T> int Array<T>::getCount(T element) {
    int count = 0;
    for (int i = 0; i < this->size; i++) if (this->ptrStart[i] == element) count++;
    return count;
}

expected output:

3 
7 
2

actual output:

3 
0 
2
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
Akshdeep Singh
  • 1,301
  • 1
  • 19
  • 34
  • Please expound upon your question. Why do you expect that output? [How to ask](https://stackoverflow.com/help/how-to-ask) – ejderuby Jul 26 '19 at 14:33
  • 1
    @ejderuby in what way is it not clear? – 463035818_is_not_an_ai Jul 26 '19 at 14:37
  • 1
    See [this question](https://stackoverflow.com/questions/49634832/getting-a-dangling-pointer-by-returning-a-pointer-from-a-local-c-style-array) for a similar issue. – 1201ProgramAlarm Jul 26 '19 at 14:37
  • @formerlyknownas_463035818 He hasn't explained the question at all. See the section under "**Introduce the problem before you post any code**" [here](https://stackoverflow.com/help/how-to-ask). But I guess certain questions don't need any explanation, because this already has five answers. – ejderuby Jul 26 '19 at 14:44
  • _"I am trying to implement `class` with `template`."_ I have no idea what this means. What actually is it that you wish to accomplish? – Lightness Races in Orbit Jul 26 '19 at 14:49
  • `T arr[size];` is a gcc extension and not standard C++ – AndyG Jul 26 '19 at 15:06
  • @LightnessRacesinOrbit on a second though you were right (with the comment that has been deleted). I allowed myself to try to improve the question – 463035818_is_not_an_ai Jul 29 '19 at 07:21

5 Answers5

4

Both of these functions are invalid:

Array(T defaultValue, int size) {
    T arr[size];
    for (int i = 0; i < size; i++) arr[i] = defaultValue;
    this->size = size;
    this->ptrStart = arr;
}

Array(T* arr, int size) {
    this->size = size;
    this->ptrStart = arr;
}

The first function sets the data member ptrStart to a local variable arr that will not be alive after exiting the function.

The second function should dynamically allocate an array and copy elements from the array pointed to by arr.

For example, the second function can be defined the following way:

Array( const T *arr, int size) : ptrStart( new T[size] ), size( size ) {
    for ( int i = 0; i < size; i++ )
    {
        ptrStart[i] = arr[i];
    }
    // or you can use standard algorithm std::copy
}

Also, you need to include a destructor and either make the class uncopyable or define at least the copy constructor and copy assignment operator.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
3

This is an error

Array(T defaultValue, int size) {
    T arr[size];
    for (int i = 0; i < size; i++) arr[i] = defaultValue;
    this->size = size;
    this->ptrStart = arr;
}

The array arr only exists in the constructor. When the constructor exits the array no longer exists. But ptrStart is pointing to this array even after it has been destroyed. So you get unpredictable results.

In C++ you must always think about the lifetime of objects. Objects don't keep existing just because a pointer is pointing at them (this is different in some other languages). If you have a pointer pointing at an object which no longer exists, that is called a dangling pointer. In your code ptrStart is a dangling pointer (when you use this particular constructor).

Also T arr[size]; is not legal C++ since in C++ array sizes must be compile time constants and size is a variable.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
john
  • 85,011
  • 4
  • 57
  • 81
2
Array(T defaultValue, int size) {
    T arr[size];
    for (int i = 0; i < size; i++) arr[i] = defaultValue;
    this->size = size;
    this->ptrStart = arr;
}

Here you set ptrStart to point at the local variable in a function. Once that function is done, the pointer is dangling and you have Undefined Behavior. Your program might crash, format your hard drive, output one or more wrong values or, possibly, output the right value.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • 1
    Don't scare him too much ;) Formatting hard drive, while isn't explicitly disallowed by the standard, is outside of the real of real possibilities. – Piotr Siupa Jul 26 '19 at 14:38
  • If dogs and cats start living together its a sure sign you have a dangling pointer, or the end is nigh. – Demolishun Jul 26 '19 at 15:15
2

Your constructor

Array(T defaultValue, int size) {
    T arr[size];
    for (int i = 0; i < size; i++) arr[i] = defaultValue;
    this->size = size;
    this->ptrStart = arr;
}

has at least 2 problems.

First T arr[size] is not standard C++ but uses a compiler extension. Also it is a local variable and this->ptrStart = arr; only assigns its adress to the member. Note that arr is an array, not a pointer, but it can decay to a pointer.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
2

The problem lies in this function:

Array(T defaultValue, int size) {
    T arr[size]; // Create array on stack - it will be deleted when the function returns
    for (int i = 0; i < size; i++) arr[i] = defaultValue;
    this->size = size;
    this->ptrStart = arr; // Storing address to array which won't exist after this line
}

After the function returns the pointer this->ptrStart points to some random place in memory and at the point when you call B.getCount(0), it can be pretty much any data in there. Reading memory pointed by that dangling pointer is an Undefined Behavior. If you are unlucky, this can even cause a runtime exception, not only wrong result. (Or maybe it is lucky, at least the result isn't wrong ;) )


To fix that problem, you should use operator new to create the array. This way, the array will be created on heap instead of stack.

T arr[size]; // bad
T *arr = new T[size]; // good

Heap is not cleared when function returns. This mean it is safe to use array from heap outside of the function it was created in.

Unfortunately, it also mean you need to delete it manually in the destructor of Array.

~Array() {
    if (createdInConstructor)
        delete[] this->ptrStart;
}

createdInConstructor is a new bool field of the class so the destructor don't try to delete the array in case of objects A & C. You could also use a smart pointer to achieve that a little easier.

Piotr Siupa
  • 3,929
  • 2
  • 29
  • 65
  • This is a very low-level view. "on stack" "points to some random place in memory" "can be pretty much any data". Fortunately you redeemed yourself later by mentioning UB. This is C++, an abstraction. So you have an automatic-storage object, an invalid pointer (subtly different!), and no particular meaning of any "data" – Lightness Races in Orbit Jul 26 '19 at 14:51
  • @LightnessRacesinOrbit This is the way I think about this stuff. If something of that is incorrect, I'll fix that. Other than that, I think it is quite useful to know how it works internally. – Piotr Siupa Jul 26 '19 at 14:55
  • Also don't recommend `new`/`delete`; we're not in the 90s any more – Lightness Races in Orbit Jul 26 '19 at 14:59
  • Also you didn't mention VLAs – Lightness Races in Orbit Jul 26 '19 at 14:59
  • @LightnessRacesinOrbit On the one hand, you're absolutely right. There are abstractions for a reason and they should be treated like that, especially if you want the code to be portable. On the other hand, I program embedded systems a lot, and there it is often very important, where is the stack, the head and where exactly some pointer points. C++ is very elastic language and you can come very close to the hardware if you know what platform you write for. – Piotr Siupa Jul 26 '19 at 15:05
  • @LightnessRacesinOrbit I know `new`/`delete` is not a good way to go and maybe I should emphasize smart pointers a little more but I think you need to understand plain memory allocation before you start to play with fancy wrappers or you end with a code full of smart pointers which "almost works". – Piotr Siupa Jul 26 '19 at 15:08
  • @LightnessRacesinOrbit Yeah, I forgot that Variable Length Arrays are compiler extersion but it doesn't really solve any problem here, anyway. – Piotr Siupa Jul 26 '19 at 15:11