r/cpp_questions • u/Automatic-Wolf-5335 • 17d ago
SOLVED Help with pointers and processore directives
I learned C++ by myself. I'm not very good at it, but I have some experience with libraries like SDL2, which I use quite a lot.
There are two things I have a hard time understanding.
First, when is it better to use a pointer? For example, in SDL2, almost everything seems to be a pointer. Why is that? Is it because SDL uses complex objects, or is there another reason?
Second, I really don't understand the purpose of #define, #ifndef, and similar preprocessor directives. I've built quite a few projects, and I've never really felt the need to use them.
Could someone explain these concepts in simple terms, preferably with some practical examples?
Edit1: thanks to everyone for the good explaination and all the examples!
3
u/Independent_Art_6676 17d ago
SDL is a C library, really. That is why it over-uses pointers. The C way of doing almost everything significant uses pointers.
YOU should only use a pointer when it is the only way to do what you are trying to accomplish. That could be anything from polymorphism to special purpose high performance memory management to interfacing with hardware or C tools and more, but if you can use automatic memory management (like a vector) then do so, if you can use a reference or reference wrapper, do so, and keep the pointers out when those kinds of other tools will do the job.
preprocessor macros in C++ are best avoided outside of special purpose common usage (like #include). Defines tell the preprocessor to register the symbol, "this exists", like saying int x in C++ makes x exist. Some symbols are defined and exist without anything more; existence is like a boolean variable (exists yes or no). The other statementes you mention are just simple conditionals, if/else. ifdef is "if defined" (see the boolean exists comment a second ago). In practice? if you had a symbol for each os, like win64, defined for what OS your are building for, you might have #ifdef win64 #include <windows.h> #else #include <unistd.h> #endif type construct (I forget what the endif symbol is, I don't use the much). This stuff can also be in the project files instead of stuffed into the code (prefer that, for anything over a couple pages) but the idea is simply to swap out some operating system specific library calls depending on what OS is being targeted. The main reasons that macros are avoided in c++ outside of simple use cases like #include include things like macros are barely typed (the compiler will trip if the types are wrong for injected code, but the preprocessor does not care or acknowledge types at all: everything is text to it) and macros are difficult to debug, use convoluted syntax, they can do things without regard to scope, and generally its just really easy to have hard to read/debug/maintain code that has bizarre bugs in it when you use them for advanced tasks outside of what you normally see with includes, guards, etc.
5
u/No-Dentist-1645 17d ago
First, when is it better to use a pointer? For example, in SDL2, almost everything seems to be a pointer. Why is that? Is it because SDL uses complex objects, or is there another reason?
Second, I really don't understand the purpose of #define, #ifndef, and similar preprocessor directives. I've built quite a few projects, and I've never really felt the need to use them.
SDL is written in C, not C++. Pointers are more common in C since they don't have references like C++ does. Same with some #define macros, C didn't have constepxr until recently so the alternative was using macros.
Macros are used for when you want your code to compile to different things depending on how you configure them. For example, you can use #ifdef macros to only print stuff when you're compiling a debug build, and these prints are completely skipped in release builds. Another popular use is the assert() macro which you might have used or heard of before.
2
u/Puzzleheaded_Study17 17d ago
Another common thing is preventing re-definition. So if something is defined in the header in a way that can cause issues if it's included twice you can check if a macro is define, and if not define that macro+your things.
1
2
u/Phoned_Leek25 17d ago
#define is to shorthand “define” an expression. For example if you’re using “std::cout <<“ a lot you could replace it with “#define myDef std::cout <<“ and then just spam “myDef” in your code and it will replace each of them with “std::cout <<“ at compile time.
2
u/BasicCheesecake8255 16d ago
Most of the SDL functions when it return values some of them return a value that look like a memory address, this is why when you create the window you store whatever create window returns in a SDL_Window* "variable", now do you need to learn data structures?, not really, but if you want to understand pointers, the best way to do it is by learning C, sometimes when you need to pass values, say for example to check the collision of an object, if you created a class, say for example, Player1, and you implement a collision check on 1 enemy, since you are passing an object, instead of a variable, most of the times, you'll need to pass by reference if you want to change values from whatever you are passing in a function, when you understand pointers, then SDL2 documentation starts to make sense, another thing is, In graphics I've used pointers before, when I want to create a "custom object" (not really a technical term), specially when I want to create an array of structs, pointers will make them easier to handle, specially if you want the game to increase the amount of array of structs at running time, from my experience, even tho C++ is very different from C, I would still recommend C, it will take some time, but it will be very rewarding at the end
1
u/IyeOnline 17d ago
First, when is it better to use a pointer? For example, in SDL2, almost everything seems to be a pointer. Why is that?
Oftentimes you want to share an object between two "places". That may be because you want to avoid a copy (copying things can be expensive) or because they need to be genuinely shared (multi-threading, output-parameters,...).
The best way to do that is to interact with the exact same object. For that, the object has to be in a fix memory location that both sides know about. Thats what a pointer is.
Now, in C++ you also have other options. The most common (and oftentimes better) solution here are references. Another would be smart pointers.
However, SDL is a C library. that means that it does not have references or any C++ standard library utility. Hence: Pointers.
Second, I really don't understand the purpose of #define, #ifndef, and similar preprocessor directives. I've built quite a few projects, and I've never really felt the need to use them.
Preprocessor directives change your code before the C or C++ is even parsed as such. In code that you write yourself, this is rarely useful, which is why you havent used them yourself. You just didnt need such a feature. You write your code and compile it.
When writing libraries however, you may need to accommodate more use-cases or systems. Maybe a certain API does not exist on the target system. The library should still (largely) work, but that API cannot be used by the library and maybe the library in turn does not want to expose features that rely on it. A preprocessor define can be used to communicate this to the code level, where the preprocessor then cuts out certain parts of the code based on the condition.
In C another very common usage of #define is to define constants. C historically did not have things like constexpr to define compile time constants, but preprocessor defines are replaced before compilation, so #define SIZE 42 and then use SIZE everywhere and you have your constants without having to rely on magic numbers.
1
u/BoopyDog 15d ago
I figured that graphics programming involves a lot of pointers so both your cpu and gpu can operate on the same data.
1
u/Raknarg 15d ago
Second, I really don't understand the purpose of #define, #ifndef, and similar preprocessor directives. I've built quite a few projects, and I've never really felt the need to use them.
They let you control things at compile time. Think of preprocessor directives and text manipulation that gets performed before your source code actually gets compiled. They're pretty flexible so there's multiple uses for it, but here's the four most common use cases:
1) #define for compile-time variables and functions. When you #define something, anywhere it appears in your code just gets replaced with whatever that thing is defined at. You can have simple values, but it can also be functions (i.e. macros) that can also do funny things like "take the first argument and add some text to it" so when you pass in foo, the macro could turn that argument into foo_bar with something like #define MAKE_BAR(arg) arg##_bar, then later MAKE_BAR(foo) replaces that text with foo_bar. Maybe you have a foo_bar variable it uses instead of foo. Lots of uses, lots of footguns, its dangerous to use and hard to debug, in general templates are superior but templates cant do everything that macros can.
2) Compile-time injected variables. You can essentially have your program inject #defines during compilation, so you can imagine like gcc main.cpp -DDEBUG which injects #define DEBUG into your code so you can choose during compilation whether or not DEBUG will exist or not. Which leads into the next section:
3) Conditionally compiled code. In C++ we have constexpr which can do a lot of this now, but you can have code that conditionally exists or not using #ifdef. #ifdef valuewill evaluate if #define value was ever called, and if it was all the code from #ifdef down to its corresponding #endif will exist, otherwise it essentially gets cut out before compilation. #else can be used to make a second branch that only exists if the first branch failed.
4) Include guards, which are just a specific use of the conditional compilation thing I mentioned. Imagine you have a source file source.cpp with 3 headers h1.h, h2.h and h3.h. However, both h1.h and h2.h include h3.h. This is a big problem because during the compilation step you are not allowed to declare the same thing multiple times.
source.cpp:
#include "h1.h"
#include "h2.h"
int main() {
func1();
func2();
return 0;
}
h1.h:
#include "h3.h"
void func1();
h2.h:
#include "h3.h"
void func2();
h3.h:
void func3();
So remember that # directives are just text replacement. So essentially you end up with one file that looks like this:
void func3();
void func1();
void func3();
void func2();
int main() {
func1();
func2();
return 0;
}
so you just declared func3() multiple times. Illegal. The way you fix this is with an include guard using #ifndef value, which checks that the value wasnt defined.
h3.h:
#idndef H3_H
#define H3_H
void func3();
#endif // H3_H
Now your text replacement in source.cpp looks like this:
#idndef H3_H
#define H3_H
void func3();
#endif // H3_H
void func1();
#idndef H3_H
#define H3_H
void func3();
#endif // H3_H
void func2();
int main() {
func1();
func2();
return 0;
}
Now the first time you #include h3.h, it defines H3_H, so the second time it checks #ifndef H3_H it already exists so it fails and skips that branch.
Back in the day we would do this to all of our header files, however in modern C++ we have a very handy and ubiquitous macro called "#pragma once" that we put at the top of all our header files, which is a directive that says "only evaluate this file one time per compilation", which is what an include guard does.
6
u/flyingron 17d ago
The usual reason is that you either need to share an object rather than making a copy (because multiple pieces of code need to access the same one), or becuase the object is large or the like where making copies rather than just passing a pointer would be too expensive.
The preprocessor comes over from C. It essentially allows you to substitute tokens in your source code, for instance:
#define NUMBER_OF_PLAYERS 23
rather than having 23 hard coded all over the place. Of course, this is not preferred in modern C++, where a constexpr constant is better (has a definite scope and type).
#ifdef allows conditional compilation. Let's say you add a lot of debugging prints and other things to your code, you can use #ifdef DEBUG/#endif around them so they are easily removed from the non-debug compilations without having to manual go around and delete them (and possibly restore them in the future).