Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

#define LIST(MACRO, ...) \ EXPAND(MACRO(dog, __VA_ARGS__)) \ EXPAND(MACRO(cat, __VA_ARGS__)) \ EXPAND(MACRO(racoon, __VA_ARGS__))

The first argument is supplied by the LIST, while the rest is provided by the user in the LIST invocation. For example:

#define FORWARD_DECLARE(name, type, prefix) type prefix##name; LIST(FORWARD_DECLARE,Animal,anim_) LIST(FORWARD_DECLARE,Object,obj_)

will expand to

Animal anim_dog;

Animal anim_cat;

Animal anim_racoon;

Object obj_dog;

Object obj_cat;

Object obj_racoon;

Section 75.4: Macros

Macros are categorized into two main groups: object-like macros and function-like macros. Macros are treated as a token substitution early in the compilation process. This means that large (or repeating) sections of code can be abstracted into a preprocessor macro.

// This

is an

object-like macro

#define

PI

3.14159265358979

//This is a function-like macro.

//Note that we can use previously defined macros

//in other macro definitions (object-like or function-like)

//But watch out, its quite useful if you know what you're doing, but the

//Compiler doesn't know which type to handle, so using inline functions instead

//is quite recommended (But e.g. for Minimum/Maximum functions it is quite useful)

#define AREA(r) (PI*(r)*(r))

// They can be used like this: double pi_macro = PI; double area_macro = AREA(4.6);

The Qt library makes use of this technique to create a meta-object system by having the user declare the Q_OBJECT macro at the head of the user-defined class extending QObject.

Macro names are usually written in all caps, to make them easier to di erentiate from normal code. This isn't a requirement, but is merely considered good style by many programmers.

When an object-like macro is encountered, it's expanded as a simple copy-paste operation, with the macro's name being replaced with its definition. When a function-like macro is encountered, both its name and its parameters are expanded.

double pi_squared = PI * PI; // Compiler sees:

double pi_squared = 3.14159265358979 * 3.14159265358979; double area = AREA(5);

GoalKicker.com – C++ Notes for Professionals

403

// Compiler sees:

double area = (3.14159265358979*(5)*(5))

Due to this, function-like macro parameters are often enclosed within parentheses, as in AREA() above. This is to prevent any bugs that can occur during macro expansion, specifically bugs caused by a single macro parameter being composed of multiple actual values.

#define BAD_AREA(r) PI * r * r

double bad_area = BAD_AREA(5 + 1.6); // Compiler sees:

double bad_area = 3.14159265358979 * 5 + 1.6 * 5 + 1.6;

double good_area = AREA(5 + 1.6); // Compiler sees:

double good_area = (3.14159265358979*(5 + 1.6)*(5 + 1.6));

Also note that due to this simple expansion, care must be taken with the parameters passed to macros, to prevent unexpected side e ects. If the parameter is modified during evaluation, it will be modified each time it is used in the expanded macro, which usually isn't what we want. This is true even if the macro encloses the parameters in parentheses to prevent expansion from breaking anything.

int oops = 5;

double incremental_damage = AREA(oops++); // Compiler sees:

double incremental_damage = (3.14159265358979*(oops++)*(oops++));

Additionally, macros provide no type-safety, leading to hard-to-understand errors about type mismatch.

As programmers normally terminate lines with a semicolon, macros that are intended to be used as standalone lines are often designed to "swallow" a semicolon; this prevents any unintended bugs from being caused by an extra semicolon.

#define IF_BREAKER(Func) Func();

if (some_condition) // Oops.

IF_BREAKER(some_func);

else

std::cout << "I am accidentally an orphan." << std::endl;

In this example, the inadvertent double semicolon breaks the if...else block, preventing the compiler from matching the else to the if. To prevent this, the semicolon is omitted from the macro definition, which will cause it to "swallow" the semicolon immediately following any usage of it.

#define IF_FIXER(Func) Func()

if (some_condition) IF_FIXER(some_func);

else

std::cout << "Hooray! I work again!" << std::endl;

Leaving o the trailing semicolon also allows the macro to be used without ending the current statement, which can be beneficial.

#define DO_SOMETHING(Func, Param) Func(Param, 2)

GoalKicker.com – C++ Notes for Professionals

404

// ...

some_function(DO_SOMETHING(some_func, 3), DO_SOMETHING(some_func, 42));

Normally, a macro definition ends at the end of the line. If a macro needs to cover multiple lines, however, a backslash can be used at the end of a line to indicate this. This backslash must be the last character in the line, which indicates to the preprocessor that the following line should be concatenated onto the current line, treating them as a single line. This can be used multiple times in a row.

#define TEXT "I \ am \

many \ lines."

// ...

std::cout << TEXT << std::endl; // Output: I am many lines.

This is especially useful in complex function-like macros, which may need to cover multiple lines.

#define CREATE_OUTPUT_AND_DELETE(Str) \ std::string* tmp = new std::string(Str); \ std::cout << *tmp << std::endl; \

delete tmp;

// ...

CREATE_OUTPUT_AND_DELETE("There's no real need for this to use 'new'.")

In the case of more complex function-like macros, it can be useful to give them their own scope to prevent possible name collisions or to cause objects to be destroyed at the end of the macro, similar to an actual function. A common idiom for this is do while 0, where the macro is enclosed in a do-while block. This block is generally not followed with a semicolon, allowing it to swallow a semicolon.

#define DO_STUFF(Type, Param, ReturnVar) do { \ Type temp(some_setup_values); \

ReturnVar = temp.process(Param); \ } while (0)

int x;

DO_STUFF(MyClass, 41153.7, x);

// Compiler sees:

int x; do {

MyClass temp(some_setup_values); x = temp.process(41153.7);

} while (0);

There are also variadic macros; similarly to variadic functions, these take a variable number of arguments, and then expand them all in place of a special "Varargs" parameter, __VA_ARGS__.

#define VARIADIC(Param, ...) Param(__VA_ARGS__)

VARIADIC(printf, "%d", 8); // Compiler sees:

GoalKicker.com – C++ Notes for Professionals

405

printf("%d", 8);

Note that during expansion, __VA_ARGS__ can be placed anywhere in the definition, and will be expanded correctly.

#define VARIADIC2(POne, PTwo, PThree, ...) POne(PThree, __VA_ARGS__, PTwo)

VARIADIC2(some_func, 3, 8, 6, 9); // Compiler sees:

some_func(8, 6, 9, 3);

In the case of a zero-argument variadic parameter, di erent compilers will handle the trailing comma di erently. Some compilers, such as Visual Studio, will silently swallow the comma without any special syntax. Other compilers, such as GCC, require you to place ## immediately before __VA_ARGS__. Due to this, it is wise to conditionally define variadic macros when portability is a concern.

// In this example, COMPILER is a user-defined macro specifying the compiler being used.

#if

COMPILER

== "VS"

#define VARIADIC3(Name, Param, ...) Name(Param, __VA_ARGS__)

#elif

COMPILER

== "GCC"

#define VARIADIC3(Name, Param, ...) Name(Param, ##__VA_ARGS__)

#endif /* COMPILER

*/

Section 75.5: Predefined macros

Predefined macros are those that the compiler defines (in contrast to those user defines in the source file). Those macros must not be re-defined or undefined by user.

The following macros are predefined by the C++ standard:

__LINE__ contains the line number of the line this macro is used on, and can be changed by the #line directive.

__FILE__ contains the filename of the file this macro is used in, and can be changed by the #line directive.

__DATE__ contains date (in "Mmm dd yyyy" format) of the file compilation, where Mmm is formatted as if obtained by a call to std::asctime().

__TIME__ contains time (in "hh:mm:ss" format) of the file compilation.

__cplusplus is defined by (conformant) C++ compilers while compiling C++ files. Its value is the standard version the compiler is fully conformant with, i.e. 199711L for C++98 and C++03, 201103L for C++11 and 201402L for C++14 standard.

Version ≥ c++11

__STDC_HOSTED__ is defined to 1 if the implementation is hosted, or 0 if it is freestanding.

Version ≥ c++17

__STDCPP_DEFAULT_NEW_ALIGNMENT__ contains a size_t literal, which is the alignment used for a call to alignment-unaware operator new.

Additionally, the following macros are allowed to be predefined by implementations, and may or may not be present:

__STDC__ has implementation-dependent meaning, and is usually defined only when compiling a file as C, to signify full C standard compliance. (Or never, if the compiler decides not to support this macro.)

Version ≥ c++11

GoalKicker.com – C++ Notes for Professionals

406

__STDC_VERSION__ has implementation-dependent meaning, and its value is usually the C version, similarly to how __cplusplus is the C++ version. (Or is not even defined, if the compiler decides not to support this macro.)

__STDC_MB_MIGHT_NEQ_WC__ is defined to 1, if values of the narrow encoding of the basic character set might not be equal to the values of their wide counterparts (e.g. if (uintmax_t)'x' != (uintmax_t)L'x')

__STDC_ISO_10646__ is defined if wchar_t is encoded as Unicode, and expands to an integer constant in the form yyyymmL, indicating the latest Unicode revision supported.

__STDCPP_STRICT_POINTER_SAFETY__ is defined to 1, if the implementation has strict pointer safety (otherwise it has relaxed pointer safety)

__STDCPP_THREADS__ is defined to 1, if the program can have more than one thread of execution (applicable to freestanding implementation hosted implementations can always have more than one thread)

It is also worth mentioning __func__, which is not an macro, but a predefined function-local variable. It contains the name of the function it is used in, as a static character array in an implementation-defined format.

On top of those standard predefined macros, compilers can have their own set of predefined macros. One must refer to the compiler documentation to learn those. E.g.:

gcc

Microsoft Visual C++

clang

Intel C++ Compiler

Some of the macros are just to query support of some feature:

#ifdef __cplusplus // if compiled by C++ compiler extern "C"{ // C code has to be decorated

// C library header declarations here

}

#endif

Others are very useful for debugging:

Version ≥ c++11

bool success = doSomething( /*some arguments*/ ); if( !success ){

std::cerr << "ERROR: doSomething() failed on line " << __LINE__ - 2

<<" in function " << __func__ << "()"

<<" in file " << __FILE__

<<std::endl;

}

And others for trivial version control:

int main( int argc, char *argv[] ){

if( argc == 2 && std::string( argv[1] ) == "-v" ){ std::cout << "Hello World program\n"

<<"v 1.1\n" // I have to remember to update this manually

<<"compiled: " << __DATE__ << ' ' << __TIME__ // this updates automagically

<<std::endl;

}

else{

std::cout << "Hello World!\n";

}

}

GoalKicker.com – C++ Notes for Professionals

407