Say I have the following header
#ifndef WINDOW_HPP
#define WINDOW_HPP
// includes...
namespace window
{
struct Window
{
GLFWwindow *handle = nullptr;
};
struct WindowCreateInfo
{
};
std::unique_ptr<Window> windowCreate(const WindowCreateInfo &info);
void windowDestroy(Window *window);
bool windowShouldClose(const Window *window);
} // namespace window
#endif
that is implemented like so
#include "window.hpp"
namespace window
{
std::unique_ptr<Window> windowCreate(const WindowCreateInfo &info)
{
std::cout << "Creating GLFW window...\n";
if (!glfwInit())
{
CHECK_GLFW_ERROR();
throw std::runtime_error("glfwInit() failed.");
}
std::cout << "Successfully initialized GLFW\n";
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
}
void windowDestroy(Window *window)
{
glfwDestroyWindow(window->handle);
}
bool windowShouldClose(const Window *window)
{
// should I check for nullptr?
// if (!window) throw std::runtime_error("...");
const bool res = glfwWindowShouldClose(window->handle);
#ifdef DEBUG
CHECK_GLFW_ERROR();
#endif
return res;
}
} // namespace window
My specific dilemma is in functions like windowDestroy and windowShouldClose. It's possible for a user to pass a nullptr, which would lead to a crash when the pointer is dereferenced.
Here's the context:
I am the sole user of this API within my application.
I have full control over the codebase and I'm confident that I will never intentionally pass a nullptr to these functions.
Even with this control, I'm trying to establish a robust design principle. I see two main approaches:
- The Defensive Approach: Explicitly check for nullptr and throw an exception.
void windowDestroy(Window *window)
{
if (!window) {
throw std::invalid_argument("windowDestroy: 'window' cannot be null.");
}
glfwDestroyWindow(window->handle);
}
- The Minimalist/Performance Approach: Don't check, and let the dereference crash. The logic is that a crash from dereferencing nullptr is immediate and obvious, while adding checks adds visual clutter for a case that should never happen.
My reasoning so far:
The defensive approach feels "safer" and provides a clearer, more professional error message.
The minimalist approach feels "leaner." Since a dereference will crash anyway, the explicit check seems redundant for an internal, controlled API. It's essentially enforcing a precondition that I, as the user, have already agreed to follow. In the case that the precondition is not satisfied, well too bad... you crash!
My question is: In the context of a private, internal API where the developer is also the user, which approach is generally considered more reasonable and idiomatic?
Is there a strong consensus, or does it ultimately boil down to a trade-off between explicit safety checks and minimalism? Are there other factors I should consider, like long-term maintenance if the project grows or the API is exposed to other developers in the future?