C++ 11
Distinguish between () and {}
A novel feature of braced initialization is that
that it prohibits implicit narrowing conversions among built in types.
double x,y,z;
int sum { x+y+z
} // Error, sum of doubles may not be expressible as int
Initilization
using parenthesis and “=” does’nt check for narrowing conversions, because that
could break too much legacy code.
int sum(x+y+z)
// OK
int sum =
x+y+z // OK
Another noteworthy characteristics of braced initialization is its immunity
to C++’s most vexing parse.
Widget W1(10)
; // Call Widget
constructor with argument 10
Widget W2() ; // Most Vexing parse ! Declares a function named
W2 that returns a Widget.!
Widget W3{} ; // Calls
Widget Constructor with no arguments.
During
constructor overload resolution, braced initializers are matched to
std::initlizer_list parameters if at all possible, even if other constructors
offer better matches.
class Widget {
public:
Widget(int i, bool b); // ctors not declaring
Widget(int i, double d); // std::initializer_list params
…
};
Widget w1(10, true); // calls first ctor
Widget w2{10, true}; // also calls first ctor
Widget w3(10, 5.0); // calls second ctor
Widget w4{10, 5.0}; // also calls second ctor
If, however,
one or more constructors declare a parameter of type std::initializer_list,
calls using the braced initialization syntax strongly prefer the overloads taking
std::initializer_lists. Strongly. If there’s any way for compilers to construe a
call using a braced initializer to be to a constructor taking a std::initializer_list,
compilers will employ that interpretation.
class Widget {
public:
Widget(int i, bool b); // as before
Widget(int i, double d);
// as before
Widget(std::initializer_list<long double>
il); // added
…
};
Widgets w2 and
w4 will be constructed using the new constructor, even though the type of the std::initializer_list elements (long
double) is, compared to the non-std::initializer_list
constructors, a worse match for both arguments!
Widget w1(10, true); // uses parens and, as before, // calls first ctor
Widget w2{10, true}; // uses braces, but now calls // std::initializer_list
ctor // (10 and true convert to long double)
Widget w3(10, 5.0); // uses parens and, as before, // calls second ctor
Widget w4{10, 5.0}; // uses braces, but now calls // std::initializer_list
ctor // (10 and 5.0 convert
to long double)
Compilers’
determination to match braced initializers with constructors taking std::initializer_lists is so strong, it
prevails even if the best-match std::initializer_list
constructor can’t be called.
class Widget {
public:
Widget(int i, bool b); // as before
Widget(int i, double d); // as before
Widget(std::initializer_list<bool> il); // element type is
// now bool
… // no implicit
}; // conversion funcs
Widget w{10,
5.0}; // error! requires narrowing conversions
Here,
compilers will ignore the first two constructors (the second of which offers an
exact match on both argument types) and try to call the constructor taking a std::initializer_list<bool>.
Calling that constructor would require converting an int (10) and a double (5.0)
to bools. Both conversions would be narrowing (bool can’t exactly represent
either value), and narrowing conversions are prohibited inside braced
initializers, so the call is invalid, and the code is rejected.
Only if there’s
no way to convert the types of the arguments in a braced initializer to the
type in a std::initializer_list do
compilers fall back on normal overload resolution. For example, if we replace
the std::initializer_list<bool> constructor with one taking a std::initializer_list<std::string>,
the non std::initializer_list constructors
become candidates again, because there is no way to convert ints and bools to std::strings
class Widget {
public:
Widget(int i, bool b); // as before
Widget(int i, double d); // as before
// std::initializer_list element type is now
std::string
Widget(std::initializer_list<std::string> il);
… // no implicit
}; // conversion funcs
Widget w1(10, true); // uses parens, still calls first ctor
Widget w2{10, true}; // uses braces, now calls first ctor
Widget w3(10, 5.0); // uses parens, still calls second ctor
Widget w4{10, 5.0}; // uses braces, now calls second ctor
Suppose you
use an empty set of braces to construct an object that supports default
construction and also supports std::initializer_list
construction. What do your empty braces mean? If they mean “no arguments,” you
get default construction, but if they mean “empty
std::initializer_list,” you get construction from a std::initializer_list with no elements.
The rule is that you get default construction.
Empty braces mean no arguments, not an empty std::initializer_list:
class Widget {
public:
Widget(); // default ctor
Widget(std::initializer_list<int> il); // std::initializer
// _list ctor
… // no implicit
}; // conversion funcs
Widget w1; // calls default ctor
Widget w2{}; // also calls default ctor
Widget w3(); // most vexing parse! declares a function!
If you want to
call a std::initializer_list constructor
with an empty std::initializer_list,
you do it by making the empty braces a constructor argument—by putting the
empty braces inside the parentheses or braces demarcating what you’re passing:
Widget w4({}); // calls std::initializer_list ctor
// with empty list
Widget w5{{}}; // ditto
An example of where the choice between
parentheses and braces can make a significant difference is creating a std::vector<numeric
type> with two arguments.
std::vector<int> v1(10, 20); // use
non-std::initializer_list
// ctor: create 10-element
//
std::vector, all elements have
//
value of 20
std::vector<int> v2{10, 20}; // use std::initializer_list
ctor:
//
create 2-element std::vector,
// element values are 10
and 20
Prefer nullptr to 0
void f(int); // three overloads of f
void f(bool);
void f(void*);
f(0); // calls f(int), not f(void*)
f(NULL); // might not compile, but typically calls
// f(int). Never calls
f(void*)
If NULL is
defined to be, say, 0L (i.e., 0 as a long), the call is ambiguous, because
conversion from long to int, long
to bool, and 0L
to void* are considered equally good
nullptr’s advantage is
that it doesn’t have an integral type. To be honest, it doesn’t have a pointer
type, either, but you can think of it as a pointer of all types. nullptr’s actual type is std::nullptr_t, and, in a wonderfully
circular definition, std::nullptr_t is
defined to be the type of nullptr.
The type std::nullptr_t implicitly
converts to all raw pointer types, and that’s what makes nullptr act as if it were a pointer of all types.
Calling the
overloaded function f with nullptr calls
the void* overload (i.e., the pointer
overload), because nullptr can’t be
viewed as anything integral:
f(nullptr); // calls f(void*) overload
For example,
suppose you encounter this in a code base:
auto result = findRecord( /* arguments */ );
if (result ==
0) {
}
If you don’t
happen to know (or can’t easily find out) what findRecord returns, it may not
be clear whether result is a pointer type or an integral type.
auto result = findRecord( /* arguments */ );
if (result == nullptr) {
…
}
there’s no
ambiguity: result must be a pointer type.
Prefer const_iterators to iterators
const_iterators are the STL
equivalent of pointers-to-const. They point to values that may not be modified.
The standard practice of using const
whenever possible dictates that you should use const_iterators any time you need an iterator, yet have no need to
modify what the iterator points to.
All that
changed in C++11. Now const_iterators
are both easy to get and easy to use. The container member functions cbegin and cend produce const_iterators,
even for non-const containers, and
STL member functions that use iterators to identify positions (e.g., insert and
erase) actually use const_iterators.
Declare functions noexcept
if they won’t emit
exceptions.
int f(int x) throw(); // no exceptions from f: C++98 style
int f(int x) noexcept; // no exceptions from f: C++11 style
If, at
runtime, an exception leaves f, f’s exception specification is violated.
·
C++98 exception specification, the call stack is
unwound to f’s caller, and, after some actions not relevant here, program
execution is terminated.
·
With the C++11 exception specification, runtime
behavior is slightly different: the stack is only possibly unwound before
program execution is terminated.
In a noexcept function,
optimizers need not keep the runtime stack in an unwindable state if an
exception would propagate out of the function, nor must they ensure that
objects in a noexcept function are
destroyed in
the inverse order of construction should an exception leave the function. Functions
with “throw()” exception specifications lack such optimization flexibility, as
do functions with no exception specification at all.
RetType function(params) noexcept; //
most optimizable
RetType function(params) throw(); //
less optimizable
RetType function(params); //
less optimizable
Use constexpr
whenever possible.
constexpr is a feature
added in C++ 11. The main idea is performance improvement of programs by doing
computations at compile time rather than run time. Note that once a program is
compiled and finalized be developer, it is run multiple times by users. The
idea is to spend time in compilation and save time at run time. The
basic idea of constant expressions is to allow certain computations to take
place at compile time--literally while your code compiles--rather than when the
program itself is run. This has an obvious performance benefit: if something
can be done at compile time, it will be done once, rather than every time the
program runs.
Values known during compilation are privileged. They may be
placed in read-only
memory,
A function be declared as consexpr
1. In C++ 11, a constexpr function should contain only one return
statement. C++ 14 allows more than one statements.
2. constexpr function should refer only constant global variables.
3. constexpr function can call only other constexpr function not
simple function.
4. Function should not be of void type and some operator like prefix
increment (++v) are not allowed in consexpr function.
·
constexpr functions can be used in contexts that demand compile-time
constants. If the values of the arguments you pass to a constexpr function in
such a context are known during compilation, the result will be computed during
compilation. If any of the arguments’ values is not known during compilation, your
code will be rejected.
·
When a constexpr
function is called with one or more
values that are not known during compilation, it acts like a normal function,
computing its result at runtime. This means you don’t need two functions to
perform the same operation, one for compile
Comments
Post a Comment