Object-like Macros
(From Marius Bancila's Blog)
These are identifiers that are replaced with a fragment of code and are often used to give symbolic names to numerical or string literals. Here is a typical example you must have seen many times.
#define BUFFER_SIZE 1024 int main() { char buffer[BUFFER_SIZE]; }
Instead of being a macro, BUFFER_SIZE could, and should, be defined as a compile-time constant.
constexpr size_t BUFFER_SIZE = 1024;
Notice it is declared as constexpr
and not just const
. The latter indicates a value that does not change, but might only be available at runtime. The former, implies constness, but is guaranteed to be available at compile-time. constexpr
values can be used in any place where compile-time constants are expected.
Object-like macros are often used to define related symbolic names, as in the following example:
#define PERMISSION_NONE 0 #define PERMISSION_READ 1 #define PERMISSION_WRITE 2 #define PERMISSION_ADD 4 #define PERMISSION_DELETE 8 void show_permissions(int const p) { if(p & PERMISSION_READ) std::cout << "can read" << std::endl; if (p & PERMISSION_WRITE) std::cout << "can write" << std::endl; if (p & PERMISSION_ADD) std::cout << "can add" << std::endl; if (p & PERMISSION_DELETE) std::cout << "can delete" << std::endl; } int main() { int flags = PERMISSION_READ | PERMISSION_WRITE; show_permissions(flags); flags |= PERMISSION_DELETE | PERMISSION_ADD; flags &= ~PERMISSION_WRITE; show_permissions(flags); }
Following the previous example, we can simply replace these with constexpr values (in a class or namespace scope):
constexpr int PERMISSION_NONE = 0; constexpr int PERMISSION_READ = 1; constexpr int PERMISSION_WRITE = 2; constexpr int PERMISSION_ADD = 4; constexpr int PERMISSION_DELETE = 8;
However, these macros, representing bitflags here, can also be replaced with an enumeration, preferably an enumeration class.
enum class permissions { none = 0, read = 1, write = 2, add = 4, del = 8 }; void show_permissions(int const p) { if(p & static_cast<int>(permissions::read)) std::cout << "can read" << std::endl; if (p & static_cast<int>(permissions::write)) std::cout << "can write" << std::endl; if (p & static_cast<int>(permissions::add)) std::cout << "can add" << std::endl; if (p & static_cast<int>(permissions::del)) std::cout << "can delete" << std::endl; } int main() { int flags = static_cast<int>(permissions::read) | static_cast<int>(permissions::write); show_permissions(flags); flags |= static_cast<int>(permissions::del) | static_cast<int>(permissions::add); flags &= ~static_cast<int>(permissions::write); show_permissions(flags); }
This code is more verbose than the original one and you might be tempted to avoid writing all these explicit casts. You can actually make it as simple as the original and avoid macros, by overloading various operators for the enumerator type. The following snippet shows the completely rewritten example.
enum class permissions { none = 0, read = 1, write = 2, add = 4, del = 8 }; inline int operator |(permissions const lhv, permissions const rhv) { return static_cast<int>(lhv) | static_cast<int>(rhv); } inline int operator &(permissions const lhv, permissions const rhv) { return static_cast<int>(lhv) & static_cast<int>(rhv); } inline int operator |(int const v, permissions const p) { return v | static_cast<int>(p); } inline int operator |(permissions const p, int const v) { return v | static_cast<int>(p); } inline int operator &(int const v, permissions const p) { return v & static_cast<int>(p); } inline int operator &(permissions const p, int const v) { return v & static_cast<int>(p); } inline int operator~(permissions const p) { return ~static_cast<int>(p); } inline bool operator==(int const v, permissions const p) { return v == static_cast<int>(p); } inline bool operator==(permissions const p, int const v) { return v == p; } void show_permissions(int const p) { if(p & permissions::read) std::cout << "can read" << std::endl; if (p & permissions::write) std::cout << "can write" << std::endl; if (p & permissions::add) std::cout << "can add" << std::endl; if (p & permissions::del) std::cout << "can delete" << std::endl; } int main() { int flags = permissions::read | permissions::write; show_permissions(flags); flags |= permissions::del | permissions::add; flags &= ~permissions::write; show_permissions(flags); }