Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
241 views
in Technique[技术] by (71.8m points)

c++ - Constexpr vs macros

Where should I prefer using macros and where should I prefer constexpr? Aren't they basically the same?

#define MAX_HEIGHT 720

vs

constexpr unsigned int max_height = 720;
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Aren't they basically the same?

No. Absolutely not. Not even close.

Apart from the fact your macro is an int and your constexpr unsigned is an unsigned, there are important differences and macros only have one advantage.

Scope

A macro is defined by the preprocessor and is simply substituted into the code every time it occurs. The preprocessor is dumb and doesn't understand C++ syntax or semantics. Macros ignore scopes such as namespaces, classes or function blocks, so you can't use a name for anything else in a source file. That's not true for a constant defined as a proper C++ variable:

#define MAX_HEIGHT 720
constexpr int max_height = 720;

class Window {
  // ...
  int max_height;
};

It's fine to have a member variable called max_height because it's a class member and so has a different scope, and is distinct from the one at namespace scope. If you tried to reuse the name MAX_HEIGHT for the member then the preprocessor would change it to this nonsense that wouldn't compile:

class Window {
  // ...
  int 720;
};

This is why you have to give macros UGLY_SHOUTY_NAMES to ensure they stand out and you can be careful about naming them to avoid clashes. If you don't use macros unnecessarily you don't have to worry about that (and don't have to read SHOUTY_NAMES).

If you just want a constant inside a function you can't do that with a macro, because the preprocessor doesn't know what a function is or what it means to be inside it. To limit a macro to only a certain part of a file you need to #undef it again:

int limit(int height) {
#define MAX_HEIGHT 720
  return std::max(height, MAX_HEIGHT);
#undef MAX_HEIGHT
}

Compare to the far more sensible:

int limit(int height) {
  constexpr int max_height = 720;
  return std::max(height, max_height);
}

Why would you prefer the macro one?

A real memory location

A constexpr variable is a variable so it actually exists in the program and you can do normal C++ things like take its address and bind a reference to it.

This code has undefined behaviour:

#define MAX_HEIGHT 720
int limit(int height) {
  const int& h = std::max(height, MAX_HEIGHT);
  // ...
  return h;
}

The problem is that MAX_HEIGHT isn't a variable, so for the call to std::max a temporary int must be created by the compiler. The reference that is returned by std::max might then refer to that temporary, which doesn't exist after the end of that statement, so return h accesses invalid memory.

That problem simply doesn't exist with a proper variable, because it has a fixed location in memory that doesn't go away:

int limit(int height) {
  constexpr int max_height = 720;
  const int& h = std::max(height, max_height);
  // ...
  return h;
}

(In practice you'd probably declare int h not const int& h but the problem can arise in more subtle contexts.)

Preprocessor conditions

The only time to prefer a macro is when you need its value to be understood by the preprocessor, for use in #if conditions, e.g.

#define MAX_HEIGHT 720
#if MAX_HEIGHT < 256
using height_type = unsigned char;
#else
using height_type = unsigned int;
#endif

You couldn't use a variable here, because the preprocessor doesn't understand how to refer to variables by name. It only understands basic very basic things like macro expansion and directives beginning with # (like #include and #define and #if).

If you want a constant that can be understood by the preprocessor then you should use the preprocessor to define it. If you want a constant for normal C++ code, use normal C++ code.

The example above is just to demonstrate a preprocessor condition, but even that code could avoid using the preprocessor:

using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...