Author Topic: C++ Question  (Read 1202 times)

Offline lordpalandus

  • Sr. Member
  • ****
  • Posts: 410
C++ Question
« on: February 28, 2016, 02:44:47 AM »
Having read a lot about local variables for blocks (functions, loops, if-else), they seem to be extremely similar to pointers, but a lot easier to manage as they are created and destroyed when the function/loop/if-else completes it's execution, rather than the programmer must make a call to delete to remove pointers and hope that they programmed correctly to not create dangling pointers.

Since a game is contained within an event loop, I'm assuming any variables declared within it, pointers or otherwise are created when the game loop begins and are destroyed when the game loop ends. I'm assuming this because a memory leak within a program ends when the program itself ends, making me assume that the pointers are local to the game loop and thus are destroyed by the game loop when it ends.

So my question is simple. Why use pointers at all to begin with, if you can manage the same overall effect with local variables inside function/loop/if-else blocks? These variables are created at runtime like pointers within these blocks, and thus function much like pointers. Unless I'm missing something, how are pointers different from other local variables?
BAM! You've been facehugged! Have a great day!

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3170
Re: C++ Question
« Reply #1 on: February 28, 2016, 10:37:24 AM »
Someone else (Hooman  :P) can probably tell you more (and correct some of what I'm about to say).  Short answer: They're situational, and if you can avoid using them, do so.

So, what are some uses for pointers?
  • Say you have a particularly large object you want to pass to some function.  It's easier/faster to pass a pointer to that object than to pass a copy of the object itself.  This also has the benefit that any changes made to the object in that function apply to the "original" object, rather than a copy you have to pass back.
  • If you want to create an array of a size unknown to you at compile time, you'll need pointers to do so.
  • You can define what's called a void pointer (void *whatever), which can point to any type of data.
This is not by any means a definitive list of what you can and can't do with pointers.  Knowing when you do and do not need a pointer is the kind of thing that comes with experience.
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline lordpalandus

  • Sr. Member
  • ****
  • Posts: 410
Re: C++ Question
« Reply #2 on: February 28, 2016, 04:15:54 PM »
Okay. Couple questions then:

1) What if you want to pass a particularly large object to some function without affecting the original object? I know that pointers act very much like (if not exactly) Call-By-Reference formal parameters for functions, but can you use a pointer to act like a Call-By-Value instead? Could pointers be used for that, or is that not even possible?

2) I have heard of dynamic arrays where their size is determined at runtime. Is that the same thing as what you are describing?

3) I didn't know that pointers were restricted to specific kinds of data unless you made them a void pointer. I'm assuming void is being used here similar to how void is used with function calls?
BAM! You've been facehugged! Have a great day!

Offline Arklon

  • Administrator
  • Hero Member
  • *****
  • Posts: 1120
Re: C++ Question
« Reply #3 on: February 28, 2016, 04:42:06 PM »
Okay. Couple questions then:

1) What if you want to pass a particularly large object to some function without affecting the original object? I know that pointers act very much like (if not exactly) Call-By-Reference formal parameters for functions, but can you use a pointer to act like a Call-By-Value instead? Could pointers be used for that, or is that not even possible?
You can, of course, dereference a pointer with operator * to copy it to a local variable/object (assuming the object class has a defined copy constructor). "Type&"-style call-by-reference parameters are pretty much just syntactic sugar around pointers - more precisely, they're like pointers that can't be reassigned to point to something else, and indirection is automated when using them.

Quote
2) I have heard of dynamic arrays where their size is determined at runtime. Is that the same thing as what you are describing?
I think he's referring to heap-allocated arrays in general. This can include static arrays whose size is determined at runtime, or more fancy vectors which are resizable arrays, etc. In fact, in C/C++, even if you define a stack-allocated or global array like array[10], "array" by itself without operator [] to access a particular element is a pointer to the start of the array. Also, when dealing with things allocated on the heap in general (not just arrays), you're always going to be using pointers, as operator new/malloc will return a pointer to where the memory for it was allocated on the heap.

Quote
3) I didn't know that pointers were restricted to specific kinds of data unless you made them a void pointer. I'm assuming void is being used here similar to how void is used with function calls?
Generally speaking, you can typecast a pointer type to any other pointer type**, but obviously you want to make sure the type is appropriate for whatever kind of thing you have a pointer to. A void* pointer is not the same as plain void - here, it's a pointer but of undefined type, as opposed to plain "void" which signifies that a function doesn't return any data.

** The exception is pointers to class member functions, which are special in that they contain extra information about the object used when the function is called that acts as "this" in the function. Getting a plain void* pointer without the object information attached to it is possible with clever workarounds, but it technically violates the C++ specification, and isn't really necessary unless you want to insert code hooks, which is a pretty advanced topic.
« Last Edit: March 01, 2016, 06:32:14 PM by Arklon »

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 3857
Re: C++ Question
« Reply #4 on: February 29, 2016, 08:09:54 AM »
Two issues cloud this topic. The main problem typically experienced in C/C++ is local variables have a static size, fixed at compile time. You need heap memory for dynamic sized data, which is managed with pointers. This is an implementation detail. It's possible for a language to implement dynamic sized local variables, it's just not done in C/C++. The other issue that comes up is lifetime. Local variables are tied to the activation record (stack frame) of a function call. If the lifetime of a variable is not tied to the lifetime of a function, it will need to be stored outside of a function's activation record. This is in a way the real use of pointers, as this issue goes beyond simple implementation details.

Code: [Select]
// Dynamic size
void SolveSomeProblem(int size)
{
int* workingMemory = new int[size];
...
}

Code: [Select]
struct Struct {
// Fields ...
};

// Dynamic lifetime
Struct* InitNewStruct()
{
Struct* s = new Struct;
// Initialize fields ...
return s;
}


Sirbomber is right about avoiding pointers if possible. Avoid both pointers and dynamic memory allocation if you can, but don't go far out of your way to do so. They solve a specific set of problems, so use them when you need to. Dynamic memory allocation is more costly than allocating space for local variables on the stack. If you ever find yourself allocating memory that is a hardcoded constant size, stop and think about it for a moment, make sure the dynamic lifetime argument applies.
Code: [Select]
void DoNotDoThis()
{
char* buffer = malloc(1024);  // Pointless use of fixed size heap memory, tied to life of function
// Use buffer somehow ...
free(buffer);  // This function is also not exception safe, since this free is skipped if an exception is thrown (memory leak)
}

void BetterWay()
{
char buffer[1024];  // Local variable, allocated on stack
// Use buffer somehow ...
}

I recommend preferring reference types over pointer types when possible. It's tempting to think of references as syntactic suger for pointers, and that isn't far off. The main difference between the two, aside from syntax, is nullability, and re-assignability. A pointer can be null, and can have it's value changed to point to different objects. A reference must be set to reference an object (expected to be non-null) when it comes into scope, and can not be re-assigned. This simplifies reasoning about references, and there will be no null checks in your code. Both can be unsafe, if abused, pointing to invalid memory or an object after it has been destructed.
Code: [Select]
        Obj obj; // Local variable

        Obj *obj1 = 0; // Ok
        //Obj &obj2 = 0; // Error (reference can't be assigned null)

        Obj &obj3 = obj; // Ok (pointing to existing constructed object)
        Obj &obj4 = *obj1; // Ok (but a bad idea, as this indirectly assigns null)

        obj1 = &obj; // Ok (re-assign to existing constructed object)
        //obj4 = &obj; // Error (reference can't be re-assigned)

        obj1->a; // Pointer field access
        obj3.a; // Reference field access

Passing large objects to functions should be done by reference (or pointer) whenever possible, for efficiency reasons. If you want the function to modify your object, then you also need to use a reference or a pointer for correctness reasons. If you don't want the function to change your object, you can pass the object as a reference to a const, or a pointer to a const. This of course restricts the function to only reading fields and calling const methods on the object. If the function needs to modify the object passed in, but not have the changes reflected in the caller, a copy needs to be made, possibly by making use of pass-by-value.
Code: [Select]
void f(SomeLargeClass obj);  // By-value, copies object at point of call
void g(SomeLargeClass &obj); // Ry-reference, reference syntax
void h(SomeLargeClass *obj); // By-reference, pointer syntax

void SomeFunction()
{
SomeLargeClass obj;

f(obj);  // By-value, object is copied
g(obj);  // By-reference, but syntax is the same as by-value
h(&obj); // By-reference, clearly indicated at point of call
}

A benefit of references, is that a const reference can capture a temporary value that might not have a memory address. Consider passing the result of "5 + 5" to a function. This works if the parameter is declared as "const int&", but not if it's a pointer type (and not if it's not const).
Code: [Select]
void f(const int& value);
...
f(5 + 5); // Ok


Pointers to void essentially disable type checking. It makes sense for something like malloc to return a void*. Similarly for other functions that deal with generic memory allocation without regard to the type. In general avoid using void* if possible. Why disable type checking without a reason? Similarly, why cast without a reason. Type casts override type checking (and also silence warnings), so avoid them too when possible. It's better for something like malloc to return a void*, than to have every point of call do a cast to the appropriate type. It's also worth pointing out that C++ broke down casting into different types for added safety and checking, albeit, the syntax is a bit more verbose. If curious, look into static_cast, dynamic_cast, const_cast, and reinterpret_cast.
Code: [Select]
S1* s1 = malloc(sizeof(S1)); // No cast needed, type is now S1*
S2* s2 = malloc(sizeof(S2)); // No cast needed, type is now S2*
S3* = (S3*)s2; // Cast type S2* to S3* (potentially dangerous)

Note that C++ added type safe memory allocators, which you should use. C++ code should use new/delete (single object) or new[]/delete[] (array) rather than malloc/free. Also, by extension to the guideline given earlier, use new[]/delete[] when you need dynamic memory for an array where the size is only known at run-time. Use new/delete when you need a single object with dynamic lifetime that isn't tied to the scope of a function that creates the object.

Offline Arklon

  • Administrator
  • Hero Member
  • *****
  • Posts: 1120
Re: C++ Question
« Reply #5 on: February 29, 2016, 02:55:57 PM »
If the lifetime of a variable is not tied to the lifetime of a function, it will need to be stored outside of a function's activation record.
Static variables are a thing, too, though they're a bit more like global variables that are attached to a particular function/scope for convenience.

Offline leeor_net

  • Administrator
  • Hero Member
  • *****
  • Posts: 1534
    • LairWorks Entertainment
Re: C++ Question
« Reply #6 on: February 29, 2016, 06:31:34 PM »
Quote
Why use pointers at all to begin with, if you can manage the same overall effect with local variables inside function/loop/if-else blocks?

Probably already been answered but I'll throw in my $0.02.

One word: Efficiency.

If you're constantly instantiating/destroying objects, you're going to have a very, very slow program.

This becomes especially problematic when you're pulling resources/data from the hard drive or from a network location where such loading operations are very, very expensive.

If you have memory leaks in your code, then your code is wrong. Memory management takes some getting used to but once you understand it you stop making the mistakes (well, you stop making them as often).

Easy solution: Smart pointers.

But that's a whole can of worms that's beyond the scope of this reply.

Simply put, use references when you want to pass around large objects, pass by value for very simple things (POD types). Deal with pointers only in the cases where you need them (variables that you need to point to different objects or dynamically reallocate memory), otherwise stick to references.
- Leeor
LairWorks Entertainment

Titanum UFO's

Offline lordpalandus

  • Sr. Member
  • ****
  • Posts: 410
Re: C++ Question
« Reply #7 on: February 29, 2016, 10:38:26 PM »
So in a nutshell, like other pieces of code, use them only when you need them. Okay, thanks for the replies all, this has been very informative and very helpful.
BAM! You've been facehugged! Have a great day!

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 3857
Re: C++ Question
« Reply #8 on: March 01, 2016, 02:14:18 AM »
If the lifetime of a variable is not tied to the lifetime of a function, it will need to be stored outside of a function's activation record.
Static variables are a thing, too, though they're a bit more like global variables that are attached to a particular function/scope for convenience.
Correct, they're stored in the same region of memory as a global, which is outside the activation record of a function, and only visibility/access is tied to the function. Interesting point actually, since if a function were to return static variables, and if static variables supported dynamic sizing (which they don't), you could write an allocator like that. That would be basically the same as allocating from the heap.

Simply put, use references when you want to pass around large objects, pass by value for very simple things (POD types). Deal with pointers only in the cases where you need them (variables that you need to point to different objects or dynamically reallocate memory), otherwise stick to references.
POD (Plain Old Data) types need not be simple nor small. :p I think I would have said intrinsic types here. A deeply nested struct (using containment, not pointers), with a complex inheritance hierarchy for the struct or its members, and taking up a large amount of memory, can still be a POD.

So in a nutshell, like other pieces of code, use them only when you need them. Okay, thanks for the replies all, this has been very informative and very helpful.
Use them exactly when you need them. Learn them. Don't feel guilty about it. When you're ready, you can also look into topics such as exception safety (in regards to memory/resource handling), RAII (Resource Acquisition Is Initialization), and smart pointers.

Offline leeor_net

  • Administrator
  • Hero Member
  • *****
  • Posts: 1534
    • LairWorks Entertainment
Re: C++ Question
« Reply #9 on: March 01, 2016, 04:58:13 PM »
Quote
POD (Plain Old Data) types need not be simple nor small. :p I think I would have said intrinsic types here.

Agreed. I misstated this.
- Leeor
LairWorks Entertainment

Titanum UFO's

Offline lordpalandus

  • Sr. Member
  • ****
  • Posts: 410
Re: C++ Question
« Reply #10 on: March 01, 2016, 05:36:15 PM »
I don't know what a POD is, as I've not encountered that term before?

What is a POD? What is not a POD?

PS: I realizing more and more that my textbook grazes the surface of the complexities of C++. It makes it appear relatively straightforward, but now I'm not so sure.
BAM! You've been facehugged! Have a great day!

Offline leeor_net

  • Administrator
  • Hero Member
  • *****
  • Posts: 1534
    • LairWorks Entertainment
Re: C++ Question
« Reply #11 on: March 01, 2016, 05:38:42 PM »
Plain Old Data.

Basically, things like int, float, char, etc. and structs/classes composed of these. Pointers are not included in this.
- Leeor
LairWorks Entertainment

Titanum UFO's

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 3857
Re: C++ Question
« Reply #12 on: March 02, 2016, 04:07:09 AM »
I was about to say "what he said", but I've since done some reading to verify a couple points and realized I was wrong about the definition.

It seems pointers can be included in a POD data type. It's the object oriented features of C++ that must be avoided. Anything expressible in C is a POD. In particular, you can mem-copy a POD and not run into issues. A mem-copied pointer is still a valid pointer. Having constructors, destructors, or virtual functions can make something not a POD. Think of a smart pointer. If you mem-copied it, you'd break the guarantees it's supposed to provide.

The exact definition of a POD changed between different C++ standards, with the C++11 standard relaxing some of the requirements. You can read up on the term PDS (Passive Data Structures) for more info. I didn't read in depth, but I think one of the changes was what type of constructor(s) being present can make something not a POD.

I'd also like that add that enums are PODs.

Offline lordpalandus

  • Sr. Member
  • ****
  • Posts: 410
Re: C++ Question
« Reply #13 on: March 02, 2016, 11:46:17 AM »
Ok. But aren't the declared structs and classes, objects, and thus no longer PODs? How does the inclusion of special functions make something a Non-POD?

Why should one care if something is or isn't a POD? I can understand one caring if a function wasn't properly black boxed, or a class not properly made into an abstract data type, but why should I care what is or isn't a POD?
BAM! You've been facehugged! Have a great day!

Offline Arklon

  • Administrator
  • Hero Member
  • *****
  • Posts: 1120
Re: C++ Question
« Reply #14 on: March 02, 2016, 03:40:20 PM »
Ok. But aren't the declared structs and classes, objects, and thus no longer PODs? How does the inclusion of special functions make something a Non-POD?
That would be like saying that only the int type is POD but an instance of an int isn't, which would be silly. The inclusion of functions in a class, well, makes it more than data, so it's not POD.

Quote
Why should one care if something is or isn't a POD? I can understand one caring if a function wasn't properly black boxed, or a class not properly made into an abstract data type, but why should I care what is or isn't a POD?
It doesn't matter all that much. In the context of structs/classes, it's about if it makes sense for it to include functions or not. If, say, you want to make a something that just describes a particular data structure in a file format, you'd typically use a POD struct for that. If you're making stuff for object-oriented programming, you're generally going to make non-POD classes. Keep in mind a class in C++ is just a struct where the default visibility is private rather than public, but by convention people use structs for POD and classes for non-POD.
« Last Edit: March 02, 2016, 03:42:18 PM by Arklon »

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 3857
Re: C++ Question
« Reply #15 on: March 02, 2016, 09:49:44 PM »
One of the big properties of PODs is being able to mem-copy them without problems. That's how I remember them. Another characteristic is that PODs support static initialization. Static initialization means you can construct a global/static object when compiling, and insert the memory for that object into the executable, without having to call a constructor to generate that object image at runtime.

Structs and classes with functions on them can still be PODs. Think of it like C where you have a struct, and an associated function that takes a struct and performs an operation on it. The existence of that function doesn't make C have non-PODs. Similarly, tying that function to a struct or class in C++ with syntactic sugar doesn't make it a non-POD type. A regular member function with a hidden "this" pointer passed for the struct is really the same as a free standing function in C that takes the struct as a parameter.

Virtual functions however, will change the layout of the class or struct. The existence of even one virtual function means the class or struct must now include a hidden virtual function table pointer. That makes it a non-POD type. It also has implications for copying that object, where things can go wrong. Consider copying the object through a base class pointer. You would end up slicing the object, losing any added fields from the most derived class, but you'd still copy the virtual function table pointer which is set for the most derived class, and those functions might access fields that were sliced away by doing a base class copy.

If an object has copy or move constructors, it may have special behaviour during copy/moves, which a mem-copy would circumvent. Think of a smart pointer that transfers ownership, setting itself to null when a new copy is made. If you mem-copied that object, you'd now have two smart pointers pointing to the same thing, both thinking they are the one sole owner. Here the mem-copy has broken the semantics of the smart pointer. This also has implications for destructors that clean up resources when an object is destroyed. If the object was mem-copied, it may now try to release those same resources twice, possibly leading to a problem. Or perhaps worse, only one of the two copies is destroyed, releasing resources, which the other copy continues trying to access.


Plain Old Data or Passive Data Structures, are data that you can simply act on. They can be copied and moved about at whim. There is no reactive nature to them when they are copied or moved about.

Offline lordpalandus

  • Sr. Member
  • ****
  • Posts: 410
Re: C++ Question
« Reply #16 on: March 02, 2016, 10:06:38 PM »
Okay. I can see now why understanding the difference between POD and Non-POD is important. Thanks for the clarification, Hooman!
BAM! You've been facehugged! Have a great day!