Skip to main content

Prefer const and inline to #define.

Item 1: Prefer const and inline to #define.
This Item might better be called "prefer the compiler to the preprocessor," because #define is often treated as if
it's not part of the language per se. That's one of its problems. When you do something like this,
#define ASPECT_RATIO 1.653
the symbolic name ASPECT_RATIO may never be seen by compilers; it may be removed by the preprocessor
before the source code ever gets to a compiler. As a result, the name ASPECT_RATIO may not get entered into
the symbol table. This can be confusing if you get an error during compilation involving the use of the constant,
because the error message may refer to 1.653, not ASPECT_RATIO. If ASPECT_RATIO was defined in a
header file you didn't write, you'd then have no idea where that 1.653 came from, and you'd probably waste time
tracking it down. This problem can also crop up in a symbolic debugger, because, again, the name you're
programming with may not be in the symbol table.
The solution to this sorry scenario is simple and succinct. Instead of using a preprocessor macro, define a
constant:
const double ASPECT_RATIO = 1.653;
This approach works like a charm. There are two special cases worth mentioning, however.
First, things can get a bit tricky when defining constant pointers. Because constant definitions are typically put
in header files (where many different source files will include them), it's important that the pointer be declared
const, usually in addition to what the pointer points to. To define a constant char*-based string in a header file,
for example, you have to write const twice:
const char * const authorName = "Scott Meyers";
For a discussion of the meanings and uses of const, especially in conjunction with pointers, see Item 21.
Second, it's often convenient to define class-specific constants, and that calls for a slightly different tack. To
limit the scope of a constant to a class, you must make it a member, and to ensure there's at most one copy of the
constant, you must make it a static member:
class GamePlayer {
private:
static const int NUM_TURNS = 5;
int scores[NUM_TURNS];
...
};
// constant declaration
// use of constant
There's a minor wrinkle, however, which is that what you see above is a declaration for NUM_TURNS, not a
definition. You must still define static class members in an implementation file:
const int GamePlayer::NUM_TURNS;
// mandatory definition;
// goes in class impl. file
There's no need to lose sleep worrying about this detail. If you forget the definition, your linker should remind
you.
Older compilers may not accept this syntax, because it used to be illegal to provide an initial value for a static
class member at its point of declaration. Furthermore, in-class initialization is allowed only for integral types
(e.g., ints, bools, chars, etc.), and only for constants. In cases where the above syntax can't be used, you put the
initial value at the point of definition:
class EngineeringConstants {
// this goes in the class
private:
// header file
static const double FUDGE_FACTOR;
...
};
// this goes in the class implementation file
const double EngineeringConstants::FUDGE_FACTOR = 1.35;
This is all you need almost all the time. The only exception is when you need the value of a class constant
during compilation of the class, such as in the declaration of the array GamePlayer::scores above (where
compilers insist on knowing the size of the array during compilation). Then the accepted way to compensate for
compilers that (incorrectly) forbid the in-class specification of initial values for integral class constants is to use
what is affectionately known as "the enum hack." This technique takes advantage of the fact that the values of an
enumerated type can be used where ints are expected, so GamePlayer could just as well have been defined like
this:
class GamePlayer {
private:
enum { NUM_TURNS = 5 };
int scores[NUM_TURNS];
// "the enum hack" ? makes
// NUM_TURNS a symbolic name
// for 5
// fine
...
};
Unless you're dealing with compilers of primarily historical interest (i.e., those written before 1995), you
shouldn't have to use the enum hack. Still, it's worth knowing what it looks like, because it's not uncommon to
encounter it in code dating back to those early, simpler times.
Getting back to the preprocessor, another common (mis)use of the #define directive is using it to implement
macros that look like functions but that don't incur the overhead of a function call. The canonical example is
computing the maximum of two values:
#define max(a,b) ((a) > (b) ? (a) : (b))
This little number has so many drawbacks, just thinking about them is painful. You're better off playing in the
freeway during rush hour.
Whenever you write a macro like this, you have to remember to parenthesize all the arguments when you write
the macro body; otherwise you can run into trouble when somebody calls the macro with an expression. But
even if you get that right, look at the weird things that can happen:
int a = 5, b = 0;
max(++a, b);
max(++a, b+10);
// a is incremented twice
// a is incremented once
Here, what happens to a inside max depends on what it is being compared with!
Fortunately, you don't need to put up with this nonsense. You can get all the efficiency of a macro plus all the
predictable behavior and type-safety of a regular function by using an inline function (see Item 33):
inline int max(int a, int b) { return a > b ? a : b; }
Now this isn't quite the same as the macro above, because this version of max can only be called with ints, but a
template fixes that problem quite nicely:
template
inline const T& max(const T& a, const T& b)
{ return a > b ? a : b; }
This template generates a whole family of functions, each of which takes two objects convertible to the same
type and returns a reference to (a constant version of) the greater of the two objects. Because you don't know
what the type T will be, you pass and return by reference for efficiency (see Item 22).
By the way, before you consider writing templates for commonly useful functions like max, check the standard
library (see Item 49) to see if they already exist. In the case of max, you'll be pleasantly surprised to find that
you can rest on others' laurels: max is part of the standard C++ library.
Given the availability of consts and inlines, your need for the preprocessor is reduced, but it's not completely
eliminated. The day is far from near when you can abandon #include, and #ifdef/#ifndef continue to play
important roles in controlling compilation. It's not yet time to retire the preprocessor, but you should definitely
plan to start giving it longer and more frequent vacations.

Comments

Popular posts from this blog

OWASP Top 10 Threats and Mitigations Exam - Single Select

Last updated 4 Aug 11 Course Title: OWASP Top 10 Threats and Mitigation Exam Questions - Single Select 1) Which of the following consequences is most likely to occur due to an injection attack? Spoofing Cross-site request forgery Denial of service   Correct Insecure direct object references 2) Your application is created using a language that does not support a clear distinction between code and data. Which vulnerability is most likely to occur in your application? Injection   Correct Insecure direct object references Failure to restrict URL access Insufficient transport layer protection 3) Which of the following scenarios is most likely to cause an injection attack? Unvalidated input is embedded in an instruction stream.   Correct Unvalidated input can be distinguished from valid instructions. A Web application does not validate a client’s access to a resource. A Web action performs an operation on behalf of the user without checking a shared sec

CKA Simulator Kubernetes 1.22

  https://killer.sh Pre Setup Once you've gained access to your terminal it might be wise to spend ~1 minute to setup your environment. You could set these: alias k = kubectl                         # will already be pre-configured export do = "--dry-run=client -o yaml"     # k get pod x $do export now = "--force --grace-period 0"   # k delete pod x $now Vim To make vim use 2 spaces for a tab edit ~/.vimrc to contain: set tabstop=2 set expandtab set shiftwidth=2 More setup suggestions are in the tips section .     Question 1 | Contexts Task weight: 1%   You have access to multiple clusters from your main terminal through kubectl contexts. Write all those context names into /opt/course/1/contexts . Next write a command to display the current context into /opt/course/1/context_default_kubectl.sh , the command should use kubectl . Finally write a second command doing the same thing into /opt/course/1/context_default_no_kubectl.sh , but without the use of k

标 题: 关于Daniel Guo 律师

发信人: q123452017 (水天一色), 信区: I140 标  题: 关于Daniel Guo 律师 关键字: Daniel Guo 发信站: BBS 未名空间站 (Thu Apr 26 02:11:35 2018, 美东) 这些是lz根据亲身经历在 Immigration版上发的帖以及一些关于Daniel Guo 律师的回 帖,希望大家不要被一些马甲帖广告帖所骗,慎重考虑选择律师。 WG 和Guo两家律师对比 1. fully refund的合约上的区别 wegreened家是case不过只要第二次没有file就可以fully refund。郭家是要两次case 没过才给refund,而且只要第二次pl draft好律师就可以不退任何律师费。 2. 回信速度 wegreened家一般24小时内回信。郭律师是在可以快速回复的时候才回复很快,对于需 要时间回复或者是不愿意给出确切答复的时候就回复的比较慢。 比如:lz问过郭律师他们律所在nsc区域最近eb1a的通过率,大家也知道nsc现在杀手如 云,但是郭律师过了两天只回复说让秘书update最近的case然后去网页上查,但是上面 并没有写明tsc还是nsc。 lz还问过郭律师关于准备ps (他要求的文件)的一些问题,模版上有的东西不是很清 楚,但是他一般就是把模版上的东西再copy一遍发过来。 3. 材料区别 (推荐信) 因为我只收到郭律师写的推荐信,所以可以比下两家推荐信 wegreened家推荐信写的比较长,而且每封推荐信会用不同的语气和风格,会包含lz写 的research summary里面的某个方面 郭家四封推荐信都是一个格式,一种语气,连地址,信的称呼都是一样的,怎么看四封 推荐信都是同一个人写出来的。套路基本都是第一段目的,第二段介绍推荐人,第三段 某篇或几篇文章的abstract,最后结论 4. 前期材料准备 wegreened家要按照他们的模版准备一个十几页的research summary。 郭律师在签约之前说的是只需要准备五页左右的summary,但是在lz签完约收到推荐信 ,郭律师又发来一个很长的ps要lz自己填,而且和pl的格式基本差不多。 总结下来,申请自己上心最重要。但是如果选律师,lz更倾向于wegreened,