When write C++, I stick to the Google C++ style guide and follow the ToTW tips. This includes not using exceptions, and instead reporting errors via a utility class such as absl::Status. Another favorite programming technique of mine, in C++, is RAII. Let’s review what are the currently available options, and how to program using those.
In my current tool chain, I’ve got GCC 12.2.0. This gives me the option to use -std=c++2b, that is C+23 with slightly incomplete implementation. For example, std::expected is available, but the monadic API like and_then and transform, are not available. Here’s a couple of examples of things I wanted to do and how I solved them for now. This isn’t to say that I necessarily think it’s the best way, but rather a diary of what I recently did. If you have a better idea, please share it!
At my job at Google, I used absl::Status and absl::StatusOr. While the Abseil project is publicly available, I realized that it’s quite a big dependency, and it would force me to use either CMake or Bazel for my builds, and I’m not keen on changing my build system just yet. I asked the upstream if there’s a chance of getting absl::Status as a standalone library; it’s not in the cards unfortunately. I asked ChatGPT to write me a replacement, and it did a pretty good job at it. It’s not half as versatile as absl::Status, but it works well enough for me. For the purpose of this blog entry, I’ll use an enum class.
There were more things I liked to use, like the CHECK macro. That I haven’t found a replacement for just yet.
Problem #1: Returning errors
enum class Error { InvalidArgument, Unavailable };
Now, let’s say that we call a function which can return either a value or an error. In C++23 we can do it using the std::expected library.
I like complete examples you can compile and run, so here goes:
// Compile with g++ -std=c++2b example_1.cc -o example_1
// Run with ./example_1
#include <iostream>
#include <expected>
enum class Error { InvalidArgument, Unavailable };
std::string ToString(const Error& e) {
if (e == Error::InvalidArgument) {
return "InvalidArgument";
}
if (e == Error::Unavailable) {
return "InvalidArgument";
}
return "Unknown";
}
std::expected<int, Error> GetInt(bool want_success) {
if (want_success) {
return 42;
}
return std::unexpected(Error::Unavailable);
}
void Example(bool want_success) {
auto result = GetInt(want_success);
std::cout << "Got ";
if (result.has_value()) {
std::cout << result.value() << std::endl;
} else {
std::cout << ToString(result.error()) << std::endl;
}
}
int main(int argc, char* argv[]) {
Example(true);
Example(false);
return 0;
}
In a real code, you’d use something more powerful than a simple enum; you’d use something that can hold both the type of error and an error string.
Problem #2: Complex initialization
If we’re not using exceptions, we don’t have a way of returning an error from a constructor. After some searching I found a pretty good way of handling it. The rationale is well explained in https://abseil.io/tips/42.
The main wrinkle was that I can only make objects wrapped in std::unique_ptr, rather than values. The idea is to make the constructor private, and only allow creating objects via a factory function. The answer I saw (I can’t find it now) deleted the copy constructors and returned a std::unique_ptr<Foo>. This is annoying, because the factory function returns a std::expected<std::unique_ptr<Foo>, Error>, and to use my Foo instance, I have to go through two layers of indirection every time. Functions that take Foo& as an argument (e.g. DoSomething(Foo& foo)), require me to type DoSomething(*foo.value()); to invoke them, and that looks confusing. Calling methods takes the form of foo.value()->Name();. This is again confusing, because we call a function named “value”, and then we use the arrow -> on it, which is used on pointers, not on values! Of course, what we got from .value() is a std::unique_ptr, which is technically a value, so it does technically make sense, but the code sure does look confusing.
#include <expected>
#include <iostream>
#include <memory>
enum class Error { InvalidArgument, Unavailable };
std::string ToString(const Error& e) {
if (e == Error::InvalidArgument) {
return "InvalidArgument";
}
if (e == Error::Unavailable) {
return "InvalidArgument";
}
return "Unknown";
}
class Foo {
public:
static std::expected<std::unique_ptr<Foo>, Error> Create(bool want_success) {
// Acquiring resources.
if (want_success) {
return std::unique_ptr<Foo>(new Foo());
}
return std::unexpected(Error::Unavailable);
}
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
~Foo() {
// Possibly cleanup, since it's RAII.
resource_ = 0;
}
std::string Name() { return "Foo " + std::to_string(resource_); }
private:
Foo() : resource_(42) {}
int resource_ = 0;
};
void Example(bool want_success) {
auto foo = Foo::Create(want_success);
if (foo.has_value()) {
std::cout << "Got " << foo.value()->Name() << std::endl;
} else {
std::cout << "Got " << ToString(foo.error()) << std::endl;
}
}
int main(int argc, char* argv[]) {
Example(true);
Example(false);
return 0;
}
Risky workaround: Move semantics
A possible way to get rid of std::unique_ptr in your factory function is enabling move semantics for your class. There isn’t an easy way to do it; while Foo(Foo&&) = default; is correct syntax, it will not do what you need. You’ll need to write logic to your destructor which will only free the resources when necessary, and you need to implement a move constructor and an assignment constructor, which will be responsible for moving resources between two objects. It’s relatively easy to get those wrong, and it’s easy to introduce bugs later, if you happen to add another resource to your class but forget to handle it in the move constructor. It’s likely better to return std::expected<std::unique_ptr<Foo>, Error> and suffer confusing-looking accesses rather than bugs in resource management.
Problem #3: Polymorphism and RAII
Building on the two earlier examples, what if we had a few subclasses and we wanted a factory function for all of these? I’d like to write something like this:
std::expected<AbstractFoo, Error> CreateAbstractFoo(bool want_success) {
return Foo::Create(want_success);
}
Alas, the compiler tells us we can’t do that. std::expected can’t hold an abstract class. We could change the factory function signature to return std::unique_ptr<AbstractFoo>, and that would work, but we would be back to using *abstract_foo.value(). A slightly ugly way of doing it that I found, was to create an intermediate class, holding a std::unique_ptr<AbstractFoo> and forwarding calls to it. Here’s a complete example:
#include <expected>
#include <iostream>
#include <memory>
enum class Error { InvalidArgument, Unavailable };
std::string ToString(const Error& e) {
if (e == Error::InvalidArgument) {
return "InvalidArgument";
}
if (e == Error::Unavailable) {
return "InvalidArgument";
}
return "Unknown";
}
class AbstractFoo {
public:
virtual std::string Name() = 0;
};
class Foo : public AbstractFoo {
public:
static std::expected<std::unique_ptr<Foo>, Error> Create(bool want_success) {
// Acquiring resources.
if (want_success) {
return std::unique_ptr<Foo>(new Foo());
}
return std::unexpected(Error::Unavailable);
}
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
Foo(Foo&&) = default; // Allows returning Foo rather than
// std::unique_ptr<Foo>.
~Foo() {
// Possibly cleanup, since it's RAII.
resource_ = 0;
}
std::string Name() override { return "Foo " + std::to_string(resource_); }
private:
Foo() : resource_(42) {}
int resource_ = 0;
};
class ConcreteFoo : public AbstractFoo {
public:
template <typename T>
ConcreteFoo(std::unique_ptr<T> a_foo) : a_foo_(std::move(a_foo)) {}
std::string Name() override {
return a_foo_->Name();
}
private:
std::unique_ptr<AbstractFoo> a_foo_;
};
std::expected<ConcreteFoo, Error> CreatePolymorphicFoo(bool want_success) {
return Foo::Create(want_success);
}
void Example(bool want_success) {
auto foo = CreatePolymorphicFoo(want_success);
if (foo.has_value()) {
std::cout << "Got " << foo.value().Name() << std::endl;
} else {
std::cout << "Got " << ToString(foo.error()) << std::endl;
}
}
int main(int argc, char* argv[]) {
Example(true);
Example(false);
return 0;
}
The constructor of ConcreteFoo is a template, so that it can be invoked with various classes, as long as they are movable / convertible to AbstractFoo.
One wrinkle to check is to add the keyword explicit to the constructor of ConcreteFoo, like so:
template <typename T>
explicit ConcreteFoo(std::unique_ptr<T> a_foo) : a_foo_(std::move(a_foo)) {}
If you do that, the code no longer compiles, you get error: could not convert a ‘Foo::Create(bool)()’ from ‘expected<std::unique_ptr<Foo>,[…]>’ to ‘expected<ConcreteFoo,[…]>’. The error message is pointing at the only line in the body of CreatePolymorphicFoo(). This is where the compiler implicitly inserts the constructor of ConcreteFoo. It’s convenient, as long as we’re aware of it. Another detail here is that we can’t get rid of std::move in the initializer. This is where we’re converting our smart pointer from the derived type to the base (abstract) type.
The ConcreteFoo class might be annoying; if you change the methods of AbstractFoo, that’s yet another class that you have to update. But it’s not without advantages. If you want, you can utilize it in testing, by writing a stub class, and injecting it into ConcreteFoo.
That’s all I have for now, the next step for me will be waiting until and_then is available.
Problem #4: text formatting
std::format seems unavailable on my system; I ended up using fmt::format. It works well, except when formatting std::chrono::time_point values on macOS.
Problem #5: C++ wrappers around C APIs
I use SDL2 in my code, which is a C API and doesn’t use RAII. In most cases, C++ wrappers are only responsible for calling one function at the end of a life of an object. For example, if you use SDL_Texture* texture, you need to run SDL_DestroyTexture(texture); to avoid leaking memory. You can write full classes to wrap SDL pointer types, but there’s a simpler way: std::unique_ptr with a custom deleter. Looking online I found a few ways of doing it, and the easiest to use was one using a struct.
namespace sdl {
struct TextureDeleter {
void operator()(SDL_Texture* tex) { SDL_DestroyTexture(tex); }
};
using Texture = std::unique_ptr<SDL_Texture, TextureDeleter>;
} // namespace sdl
I like the elegance of this solution. Instantiating the wrapper is easy:
sdl::Texture texture(SDL_CreateTexture(renderer, ...));
if (texture == nullptr) {
// error handling here
}
When you need to pass a raw pointer to your texture when using other SDL API functions, you use texture.get().
Conclusion
- absl::Status replacement: had ChatGPT write me a subset of functionality
- absl::StatusOr replacement: std::expected is slightly more verbose, but it works
- CHECK macro: had ChatGPT write me a subset of functionality
- RAII and complex initialization: factory functions
- Text formatting (absl::Sprintf replacement): fmt::format