Neither raw pointers nor references are inherently "safer" than the other in C++. Both can lead to undefined behavior if misused, specifically through accessing invalid memory (dangling pointers/references or null pointer dereferences). Safety comes from careful design, particularly regarding object lifetimes and ownership.
Let's compare them in the context of parent-child communication, like our Button
needing to know about its UI
 manager.
UI& UIManager
)As used in the lesson after the modification:
// Button.h
class UI;
class Button : public Rectangle {
// ...
Button(UI& UIManager, const SDL_Rect& Rect);
private:
UI& UIManager; // Reference member
};
// Button.cpp
Button::Button(UI& UIManager, const SDL_Rect& Rect) :
UIManager{UIManager}, // Must be initialized
Rectangle{Rect}
{}
void Button::SomeMethod() {
// Can directly use UIManager, assume it's valid
UIManager.SetRectangleColors(...); // Use '.'
}
// UI.h
class Button;
class UI {
// ...
private:
// Button member initialized with *this
Button C{*this, {250, 50, 50, 50}};
};
Pros:
Button
) is created. This guarantees that the Button
starts its life referring to a specific UI
object.UIManager
, making the code slightly cleaner.Button
must belong to a UI
).Cons:
UI
object is destroyed while the Button
still exists, the UIManager
reference becomes dangling, and using it leads to undefined behavior (as discussed in another FAQ). Lifetime management is critical.UI
object later (usually not a requirement for a parent link).UI* pUIManager
)Alternatively, the Button
could store a pointer:
// Button.h
class UI;
class Button : public Rectangle {
// ...
Button(UI* pUIManager, const SDL_Rect& Rect);
private:
UI* pUIManager; // Pointer member
};
// Button.cpp
Button::Button(UI* pUIManager, const SDL_Rect& Rect) :
pUIManager{pUIManager}, // Initialize pointer
Rectangle{Rect}
{}
void Button::SomeMethod() {
// MUST check for null before use!
if (pUIManager) {
pUIManager->SetRectangleColors(...); // Use '->'
} else {
// Handle case where manager is null (optional?)
}
}
// UI.h
class Button;
class UI {
// ...
private:
// Button member initialized with 'this' (address-of)
Button C{this, {250, 50, 50, 50}};
};
Pros:
nullptr
, clearly indicating that the relationship might be optional or not yet established.UI
object or nullptr
later (less common for parent links).Cons:
nullptr
before dereferencing it (pUIManager->...
). Forgetting this check leads to undefined behavior (often a crash).UI
object is destroyed, pUIManager
still holds the old memory address (now invalid). Accessing it is undefined behavior, just like with a dangling reference.>
operator instead of .
, and explicit null checks.Safety: Relies entirely on how you manage object lifetimes and whether you perform necessary checks (null checks for pointers). A dangling reference is just as bad as a dangling pointer or a null pointer dereference.
Use Case:
Button
must have a UI
manager) and established at construction, and the lifetime of the referenced object (UI
) is guaranteed to exceed or equal the lifetime of the referencing object (Button
). The non-null guarantee simplifies usage code.Button
might have a UI
manager, or it might be set later), or if you need the ability to reassign the relationship. Remember to perform null checks diligently.For the lesson's scenario where the Button
is a member of UI
, the UI
's lifetime naturally includes the Button
's lifetime. Using a reference (UI&
) passed via *this
during construction is a very common and reasonable pattern, clearly signaling the mandatory, non-null relationship. The primary responsibility remains ensuring the UI
object itself isn't destroyed prematurely.
Answers to questions are automatically generated and may not have been reviewed.
Learn to create interactive buttons in SDL2 and manage communication between different UI components.