How about this: let's just say everything one didn't want to know about that demo code. It's quite provocative, as far as demo code goes. It cuts right through the writers' block, powerful enough to unleash a torrent of words that would make a dam failure feel about as frightening as it is when observed by the orbiting crew of the ISS. A couple hundred of miles of vacuum certainly gives one perspective.
But we shouldn't be mean about it. Well, I can't. Someone wrote that demo code, and on the off chance it was me (who knows what demo code I wrote that I don't remember anymore) - it wouldn't do to be mean to oneself. So we shall take it easy.
range
is an array of pointers. But you shouldn't be using any of it anyway, most likely.
Your variable-length array has a name: std::vector
. Just use that. It works great.
But back to pointers etc.: an array of pointers is like a very cut-down address book. Each pointer is someone's - or something's - address. When all you have is an address book, you don't have your friends in it. You just have their addresses. You may also happen to own these friends' homes and the address book then lists what you own. But that requires land purchases. Or, in programming speak: memory allocation and ownership.
So, in order to have an address, first someone needs to buy the land. Some memory must be allocated, and then you can put the address to it in the address book. First one on page zero (index zero), second one on page one, and so on.
As for what the code is trying to accomplish: something silly, maybe?
float range_[] = {0, 100};
The first line is obvious enough: It's a piece of land. Two acres. There's a big sculpture of a zero on one of the acres - the one closer to you. A sculpture of a 100 is on another acre. It's not all written out - the compiler "fills in the blank". The first line really means this:
float range_[2] = {0, 100};
Now to the second line:
const float* range[] = {range_};
You probably know where it's going now: let's fill in the blanks:
const float* range[1] = {range_};
The second line is that address book. It also takes some space: after all, even an address book won't fit on zero acres. Nothing would. So you need a bit more than no space to store the address book, but it can be much, much smaller than the things it holds the addresses of. Even if the acres are relatively small (float
s are not at all spacious, as far as things go). And the address book has room for exactly one address, and that address is of the two-acre parcel with the two number sculptures on it. But wait: the address in the address book says nothing about two acres. It just says that it's an address of some acres. It may even be an address of no acres whatsoever. Such address is denoted by nullptr
.
The address book also has an annotation that you're forbidden to mess with that piece of land. In C-speak, it's a pointer to a constant value. Or a bunch of constant values. Or none. See where it goes with pointers? It's really hard to know whether what you're doing is OK. It's someone's address all right, well - maybe. It may be no address, or an address of a hostel where an entire school field trip's worth of students is packed, or maybe an address of an empty phone booth. You don't believe me? Here:
double phoneBooth = std::nan("empty");
double *i_hope_my_friends_are_there = &phoneBooth;
This is real C++. It compiles. It means pretty much what I said. If you need to denote an empty phonebooth and all you got is a double, it'll do in a pinch. You'd probably want to put it in some demo code, for added effect. So, now you see why pointers can be awful. And doubles. Doubles are awful of course, but floats are even awful-er. Oh - unless you're a mainframe programmer. Then this should be totally familiar:
float pbth = nan("MPT");
Also real C++ code, except someone put using namespace std
somewhere earlier. That is just about wordy enough to be acceptable to anyone who groks COBOL. The definition of pbth
stands out in stark contrast against the COBOL background. It reeks of System 360 assembly.
But we're getting distracted. Back to the range_
! Nobody said anything about what the people who own the range_
property will do with it. They may just dynamite the whole place up. All you know is that you are forbidden from changing anything on that property - if all you got is that address book. So - maybe you do know for certain that you also really just own that piece of land, and then you could be the one dynamiting it. But it's hard to tell if all you have is that address book. It just says:
- You can come look at the sculptures, and oh - by the way - the exhibition is changing at random times throughout the day, but only if you keep buying tomatoes. (It will make 1% more sense, soon I promise)
Now hold on a second: why is it that given an address book you'd think of two dimensions, just because the things you got addresses of have a dimension themselves? You don't need to think that way at all. It's nothing that would come to anyone naturally, at least (mathematicians excepted). You'd only want to think that way if you model a problem that actually has two dimensions (spatial, vector-space-ish, or something like that). Otherwise, there's nothing very much "2D" about it. You'd hardly think that a matrix is a "two dimensional" object.
If you had an actual address book with addresses of your friends written in, and went around talking that your address book is 4D because it's a "1D" list of postal addresses in a presumably 3D space, people might look funny at the whole thing. It's not a very practical view. Except: It's only practical if it helps you deal with the problem. But it shouldn't be taken for granted to be helpful to you, just because some other people seem to be helped by it. The irony in the admonitions of this paragraph is hopefully laid bare, finally.
And indeed, in practice if you got a rectangular area with discrete areal units ("squares"), you think of it as having two dimensions, yet often the most compact way to store it is not by chunking it up into smaller pieces, throwing them around in the wind, seeing where in the neighborhood do they fall, and collecting the addresses they rest at. That's - unfortunately - how students, and often their hapless professors - expect an array of pointers to be used. Instead, just get one long chunk big enough, and keep the address of that. Or even better, fence it in, plant some tomatoes, and have them collected in the order you desire, letting the methods deal with details.
Now your needs may be fancier than this, but as long as the rectangular area is "small", say quite a bit less than a hundred items like floats, keeping it all in one chunk and moving the elements around may be the fastest way to deal with it. I mean: if you care about real performance, and not some mathematician's idea of how a shadow of a computer ought to behave given such a task. There are very few practical computers in widespread use that are sophisticated enough to live up to such philosophy.
An AVR-based Arduino has some seriously well-defined shadows as far as complexity theory goes, and you may find that a linked list behaves a bit like you had been taught in an algorithms course. But run the same program on your PC, tweak it a bit, and you have corner case in which it may perform worse than on the Arduino. I kid you not - it sure would be a pathological case, but it's not very hard to concoct. People with vocabulary much fancier than mine call it "a denial of service", but I'm not sure to what extent throwing tomatoes at people is denying them a meal, as opposed to say feeding them tomatoes.
And then you rewrite said program to act in a way that would make it 10x slower on an Arduino, yet it'll be thousands of times faster on a PC. I'm absolutely not kidding here.
Going back to that tattered address book from your question: it isn't very useful as-is, because it has one page, for crying out loud. And there's no way to change that. Single page, hardbound, is all you got. Need to store more addresses? Go get another one. You might as well just store the one address without the whole rigmarole of an address book. A single piece of paper - no bindery work needed. It can be a single address, not an array with a single element. Like this:
const float *range = range_;
But of course you're writing C++. And all of this is silly. If all you need is a piece of land that you can easily resize, use the vector:
struct MyClass {
std::vector<float> range;
};
It's in a struct, not a class, only because it tells something to humans - to the compiler the two are the same thing really. What it tells humans is: here's the property. There's no oversight. You got it, wide open, you do what you want. Got an excavator and feel like digging the place up? Go right ahead. Unless it's a const struct - then it's forbidden. Of course. But not always. It's forbidden if the object itself is const. But if the object is not const, but you got a pointer to it labeled "pointer to const", you've got a back gate. const_cast
, fire up the excavator, go. It's OK. It's not UB.
Usually we reserve the keyword class
for uses where the property is managed somehow. The land would be behind a fence, and a bunch of people standing on the fence would be selling you tomatoes that grew on the land. They'd be likely offended if you called them methods to their faces, but among software engineering folk, such talk is commonplace. Degrading stuff that software engineering.
But suppose that maybe you do want a vector of vectors:
using A_vector_of_vectors = std::vector<std::vector<float>>;
you own city blocks, each with a number of properties on them. As with city blocks, this only makes sense if the blocks are somewhat separated and not intertwined, and somewhat independent: they truly grow (or not) at their own rate, and their size demands vary. If you wanted blocks that always are accessed together, and have some regular pattern to their sizes (all equal, or in arithmetic or geometric progression - something like that), you'd probably want just one big city block instead - with subdivisions only delineated on a map of some sort, instead of delineations by, say, road work and detours.
You can own zero or more city blocks: that's the outer vector. Each city block can have an arbitrary size - even zero size. When it has zero size (is empty), then all you got is a "name" for it - something to call it by, so that you can later say "hey, we want the Nomish Quarters sized to fit four nomes, please!". And that'd be growing that one particular inner vector to size 4.
We can denote the particular contents of those vectors and subvectors in a simple notation. Suppose there are three vectors, first one with no elements, second one with one element, third one with two elements. You'd write it down like this: {{}, {50}, {80, 90}}
. Easy enough. Now we can use this to pre-fill the vector of vectors:
struct MyClass {
A_vector_of_vectors vov{{}, {50}, {80, 90}};
};
This is a recipe for making MyClass
: it's just a platonic idea at this point. Sort of like a recipe for vegan cutlets, adorned with an immaculate picture. They come out fit to be shoe soles the first time you make them. Subsequently they tend to be awesome, though. Yes, the class doesn't exist but as a shadow. If you want to actually have one to eat, you have to create an instance of it:
MyClass anInstance;
Now we got a little city, with a group of three city blocks, the first one 0 acres in size, the second one 1 acre in size, the third one 2 acres in size. And they are quite accommodating. You can resize each one of them. You can rearrange their order, or the order of the acres within. You can add new ones. Remove old ones. And the best thing? You have someone else do all the work. The std::vector
and the compiler take care of details. When you don't need anInstance
anymore, you just nuke it. You end the scope, and so ends the city. Just like that.
int main() {
MyClass anInstance;
} // bye bye, city
Look ma, no malloc
, no free
. The tomato people did it all for us!