0

I am new to c++ and trying to understand pointers. I read a example somewhere, which is like this:

#include<iostream>
#define N 5

using namespace std;

class Test {
   int x, y;
 public:
   Test(int a, int b) 
    : x(a), y(b) 
   { 
   } 

   void print() 
   { 
    cout << x << " " << y << endl; 
   } 
};

int main() 
{ 

Test **arr = new Test*[N]; 

for (int i = 0; i < N; i++) { 
    arr[i] = new Test(i, i + 1); 
    } 

for (int i = 0; i < N; i++) { 
    arr[i]->print(); 
    }

} 

So, in the line

 Test **arr = new Test*[N]; 

as far as i understand, **arr means that it's a pointer to a pointer which points to a object. So when we assign it to new Test*[N], does it means *arr stores address of N object pointers?

And if it is correct, then how can i print the address of a object from the array of objects? let's say i want to print the address of the object Test[3]?

abhis1312
  • 23
  • 3
  • There is an `operator<<` overload for pointers that will print the hex value of the pointer. To print the value of a pointer in `arr[3]`, just do `std::cout << arr[3];` – JohnFilleau Oct 26 '20 at 14:57
  • 2
    I think the alignment of `Test **arr` is unfortunate, because I normally think of it as `Test** arr`. There's no `**arr` of type `Test`. There IS a `Test**` named `arr`. A `Test`, a `Test*`, and a `Test**` are all distinct types in C++. – JohnFilleau Oct 26 '20 at 14:59
  • 1
    In this case the array is *arr, so you would print the pointer `arr[3]` – stark Oct 26 '20 at 14:59
  • *"does it means *arr stores address of N object pointers?*". `arr` is an array of N `Test*`'s, or `arr` holds N pointers to objects of type `Test`. – JohnFilleau Oct 26 '20 at 15:01
  • 1
    this example isn't for beginners, at least it is nothing you should use as example for good practices. It displays some bad practices, in particular there is no reason to use a single pointer here. Be careful with tutorials you find online, most of them are crap – 463035818_is_not_an_ai Oct 26 '20 at 15:01
  • 2
    `Test** arr;` • `arr` might be a pointer to a pointer that points to a Test; or it might be a pointer to an array of pointers that each point to a Test; or it may be a pointer to a pointer that points to an array of Test; or it may be a pointer to an array of pointers that each point to an array of Test. And any of those aforementioned pointers may be `nullptr`. – Eljay Oct 26 '20 at 15:03
  • @JohnFilleau: Actually, there is a `**arr` of type `Test`. After declaration and initialization, `**arr` is a `Test`. Declaring a `Test**` (by putting the `*`s with the type) leads to confusion when you have declarations like `Test** foo, bar;`; that's not two `Test**`s, that's a type for which `**` gets a `Test` (`foo`) and a type which is already a `Test` (`bar`). Attaching the `*`s to the name makes that more clear: `Test **foo, bar;`. – ShadowRanger Oct 26 '20 at 15:07
  • 1
    @Shadow while valid code, anyone who does that type of declaration on a single line should be drawn and quartered. Any fair society will have appropriate laws to handle people like that. Now, let's argue about curly brace placement. – JohnFilleau Oct 26 '20 at 15:08
  • @JohnFilleau: Unfortunately, a huge percentage of new C and C++ programmers do it, then come here because they don't understand why `*bar`/`bar[0]` is exploding on them, so you'll have a lot of folks to draw and quarter. :-) I consider attaching the `*`s to the name, not the type (even when there is only one name being declared), to be a best practice in line with using prefix increment by default (even when the type is a primitive for which postfix increment is harmless and the result isn't used, e.g. `for` loop increments); you want to develop habits that lead to correct code. – ShadowRanger Oct 26 '20 at 15:12
  • *"so you'll have a lot of folks to draw and quarter."* get me an axe. – JohnFilleau Oct 26 '20 at 15:14
  • @JohnFilleau thank you for helping. So, does `Test* arr` means that arr is pointer to a object of type `Test` and `Test** arr` means arr is a pointer that points to a object of type `Test`, right? – abhis1312 Oct 26 '20 at 15:20
  • @abhis see Eljay's comment above for an exhaustive list of possible interpretations for the generic case. – JohnFilleau Oct 26 '20 at 15:22
  • `T*` is pointer to `T`, and you can apply this recsurively as deep as you like. `T*` is pointer to a `int*` when `T==int*` for example – 463035818_is_not_an_ai Oct 26 '20 at 17:39
  • Pointers are objects (so are integers, floats, and arrays, to name a few). Pointers to pointers work exactly the same way as pointers to any other kind of object. – n. m. could be an AI Oct 27 '20 at 08:03

2 Answers2

0

In modern C++, there's are better alternatives than having owning raw pointers, and better alternatives than manually new'ing up an array.

There are containers (such as std::vector), there are smart pointers (such as std::unique_ptr and std::shared_ptr).

Polymorphism needs to be done through a pointer (including smart pointer) or reference, but that is not a factor in the posted code.

The OP posted code has a memory leak, since it did not clean up the arr. In these kinds of small programs, memory leaks are sometimes handwaved away and ignored.

For the code you posted, I'd have done it this way, which eliminates the pointers altogether:

#include <iostream>
#include <vector>

using std::cout;
using std::vector;

constexpr int N = 5;

class Test {
    int x;
    int y;
public:
    Test(int x_, int y_)
        : x{x_}, y{y_}
    { }

    void print() const {
        cout << x << " " << y << "\n";
    }
};

int main() {
    vector<Test> arr;

    for (int i = 0; i < N; i++) {
        arr.emplace_back(Test{i, i+1});
    }

    for (auto const& test : arr) {
        test.print();
    }
}

I had to add a little const correctness. I relied on vector and have no need for a pointer or smart pointer, in this particular example.

In my last decade of programming C++, I've never needed to use a new or an owning raw pointer.

Raw pointers are still suitable for non-owning parameters which could be nullptr, and non-owning return values which could be nullptr. But since they are non-owning, there's no concern over memory management.


Update:

"... trying to understand pointers..."

Caveat. Unfortunately, my example of "how I'd do this kind of program" does not lend itself to the OP's goal of trying to understand pointers. That kind of topic is actually bit broad, and should be thoroughly covered in the first chapter or two of a good C++ book.

Eljay
  • 4,648
  • 3
  • 16
  • 27
  • 2
    OP's code constructs the objects in-place in the data structure, so yours should too. Use `emplace_back` rather than `push_back`. – Ben Voigt Oct 26 '20 at 15:20
  • the guy asks: "how can I use pointers?" and you answer is: "look here how to use something else"... you didn't answer his question at all – vmp Oct 26 '20 at 15:43
  • @Eljay thank you for the suggestions. But in the OP's code, `Test** arr = new Test*[N]`, does the expression `new Test*[N]` mean that `arr` is a pointer to an array of pointers that points to N objects of type `Test`? – abhis1312 Oct 26 '20 at 15:46
  • @abhis1312 • No, `arr = new Test*[N]` means that `arr` is a pointer to an array of pointers. None of those pointers in the array point to anything yet, they are uninitialized. – Eljay Oct 26 '20 at 16:50
0

The answer to the first question is YES.

To the second question you can just send the pointer to cout without dereferencing it.

 cout << arr[3];

Or use C's printf function:

 printf("%p", arr[3]);

If you want to print it from the method, you use the this pointer:

void print() 
{
    cout << this <<  "- " << x << " " << y << endl; 
}
vmp
  • 2,370
  • 1
  • 13
  • 17