hello and welcome to my list of newbie c
plus habits c plus is an incredibly
complex language with a lot of history
so whether you're an actual noob that
really needs to look out for these
things or whether you just want to
improve your code a little bit let's get
started
newbie thing number one using namespace
standard people generally use it to save
typing so you can say things like string
instead of std colon colon string if
it's limited to just a single function
that might not be that bad but let's be
honest it's usually used at the global
level even worse if you do this in a
header file then you also force this
choice upon everyone that uses your code
so please don't do this in a header file
and also consider just using the names
that you actually use
number two using standard end line
especially in a loop you probably meant
to just print out a new line but endline
does more than that it also flushes the
buffer which takes extra time instead
just use a new line character number
three using a for loop by index when a
range-based for loop expresses the
intent better in this case i don't
really care at all about the index
instead use a range-based for loop
there's no index so one less chance for
an accidental typo or off by one error
number four using a loop when a standard
algorithm already exists to do what
you're trying to do
there's some simple thing that you want
to do like find the index of the first
positive number in a vector
it's so simple though you decide to just
write it yourself
instead consider if there's an algorithm
that already does what you're trying to
do
in this case we can use standard find if
to find where the first positive element
is number five using a c style array
when you could have used a standard
array c style arrays often decay into
pointers and require you to pass the
length of the array along with the array
itself this is just another opportunity
to make a typo instead use a standard
array
number six any use of reinterpret cast
pretty much the only thing you're
allowed to do with an object that you
got from reinterpret cast is to
reinterpret cast it back to the original
type
almost everything else is undefined
behavior
and yes the same goes for c style
casting so sorry famous quake 3
algorithm for computing the inverse
square root reinterpreting the bytes of
a float as a long is actually undefined
behavior you're always allowed to
reinterpret cast the address of an
object as a character type this allows
you to inspect or print out the bytes
that make up the object so you can see
its memory layout however as of c plus
20 this use of reinterpret cast is also
not necessary
bitcast interprets the bytes of one
object as a different type in this case
i can cast any object to an array of
bytes of the same size number 7 casting
away const this function takes in a map
that maps strings to the number of times
that they've occurred
it takes two words and then returns back
to you whichever one has a higher count
the first way you might try to implement
this is by looking up the counts of the
two words and then if the first count is
bigger than the second then returning
the first word if i try to compile this
i get a weird error message telling me
that the method isn't marked const
and that's how we ended up with this
code i know that i'm not modifying the
map right
the correct thing to do in this case is
not to cast away const but to instead
use the at method at is a const version
of operator square bracket that throws
if the word isn't in the map
but you might ask why don't they just
add a const version so that this would
compile
this brings us to newbie thing number
eight not knowing that operator square
brackets inserts the element into the
map if it doesn't already exist
that's right simply trying to look up
this word in the map actually inserts it
with a count of zero into the map
number nine ignoring const correctness
this function loops over a vector simply
printing out each element one element
per line
it doesn't modify the vector
so we could and should mark the vector
const this is doubly important for a
function parameter so that the caller
knows that they can use this function
without their vector being modified
number 10 not knowing about string
literal lifetimes
string literals like this one are
guaranteed to live for the entire life
of a program
so it's perfectly fine to return this
even though it looks like it's a
reference to a local variable
number 11 not using structured bindings
here we have a map of color names to
their hex values then we just loop over
all the pairs and print out the name and
hex value
it would be a lot more readable if we
could refer to these things as name and
hex rather than pair.first and
pair.second
well that's exactly what a structured
binding lets us do
we grab the pair by reference then the
structured binding introduces name and
hex as names for the first and second
elements of the pair structured bindings
can also be used with your own types if
the members are public the names get
assigned to the variables according to
the order of their declarations
number 12 using multiple out parameters
when you want to return multiple things
from a function
instead create a basic struct and give
those things names then you can return
the multiple values that you wanted to
by returning the struct instead
the caller can even make it look like it
was multiple values returned by using
structured bindings 13. doing work at
runtime that could have been done at
compile time
here's a function that gives the formula
for the sum of the first n integers
sometimes the parameters might be known
at compile time and you could do the
calculation ahead of time go ahead and
let the compiler know that it's totally
fine to compute this ahead of time
number 14 forgetting to mark
destructor's virtual if a derived class
gets deleted through a pointer to this
base class then the derived class
destructor will not be called only the
base classes destructor will be called
here i have a class that derives from
the base that doesn't have a virtual
destructor
this function expects a pointer to the
base class it uses some base
functionality and then deletes the
pointer when it's done
this will happen automatically since i'm
using a unique pointer but the same
would be true if you just took in a
normal pointer and then manually called
delete
if you pass a pointer to an instance of
this derived type then the wrong
destructor gets called at the end
to make sure the correct destructor is
called even through a pointer to a base
class you need to mark the function
virtual
it's also good practice to explicitly
mark the derived classes destructor as
override
number 15 thinking that class members
are initialized in the order they appear
in the initializer list reading left to
right this looks fine the actual order
that members are initialized in is the
order that they're declared in first we
initialize end as start plus size but
this m start is garbage it hasn't been
initialized yet we can of course fix
this by declaring start first instead
or since start is also a parameter to
the function we could just define the
end variable in terms of the start
parameter
16 not realizing there's a difference
between default and value initialization
x and x2 are default initialized they
contain garbage until you put something
into them
y y2 and y3 are value initialized they
are guaranteed to contain the value 0. z
on the other hand is neither default nor
value initialized this is a function
declaration even if it's a tiny bit less
efficient initializing your values is
almost always a good idea the same kind
of thing goes if you have an aggregate
or array type
in the first two cases n m will be
garbage and s will be the empty string
in the last three cases n and m are zero
and s is the empty string
notice that even with default
initialization the empty string did
still get initialized
that's because for both default and
value initialization if you define a
default constructor it will be called
17. overuse of magic numbers
introducing a basic constant in your
code can make it many times more
readable
the compiler is going to optimize it
away anyway just give it a good name
18. attempting to add or remove elements
from a container while looping over it
well doing that is sometimes just
necessary but what i mean is noobs often
do it incorrectly
we're trying to put a copy of the vector
at the end of the vector
adding or removing an element to the
vector may invalidate the iterators to
the vector
for example pushback might need to
resize the vector and move all the
elements to a new location
after moving the contents of the vector
to a new location you can't expect the
end pointer to be the same
you would run into the exact same issue
in fact it's probably clearer why you
would run into this issue if you used
iterators directly
this is a case where using a loop index
actually does solve the problem
it doesn't matter if the contents of
your vector get moved somewhere else the
i element is still the i element
number 19 returning a moved local
variable
i totally get where you're coming from a
vector can be a large object and you
don't want to make a copy of it
so you go ahead and try to move it out
if you had just tried to return v
directly there would have been no copy
and no move
in this situation that's because of
return value optimization
but what if the compiler can't do return
value optimization
in all cases the move is unnecessary the
compiler always knows that it can move a
local variable
but in some cases this actively prevents
return value optimization so that's why
this is one of the few rules where i can
say you should just never do this which
brings us to number 20 thinking that
move actually moves something
here is an implementation of standard
move
the full templated definition might be a
bit much to take in all at once so let's
take a look just at the int case
move takes in an int l value reference
static casts it to an r value reference
and returns it
the exact same thing happens in the r
value overload it just static casts to
an r value and returns it
a more accurate name for move is
probably something like cast to r value
number 21 thinking that evaluation order
is guaranteed to be left to right
here's a famous example we have a string
that says but i've heard it works even
if you don't believe in it
we replace the first four characters
with the empty string
then we find even and replace it with
only
then we find don't and delete it
with this reasoning we should end up
with i've heard it works only if you
believe in it
but prior to c plus 17 the compiler is
actually allowed to compute any
sub-expression in any order
so theoretically it could find the
location of even first
and then replace the first four
characters making that location off by
four
so then when the second replace happens
it would replace these four characters
with only
and you can see how this goes on you
don't get the result you expected
well the good news is that as of c plus
17 this example is guaranteed if you
have a dot b
then a is guaranteed to be evaluated
before b is
however even in c plus 20 the order that
function arguments are evaluated is
still not guaranteed left to right
this wouldn't matter much if a b and c
were pure functions
but if a b and c have side effects then
the order that they're called in might
actually make a difference
22. using totally unnecessary heap
allocations when a stack allocation
would have been fine
here we create two customer records on
the heap
then we do some work and then we end up
deleting those variables at the end of
the function
the question we should ask ourselves is
did this really need to be a heap
allocation
there's a good chance it would have been
fine if we just stack allocated them
so let's just say for the sake of
argument that these objects are too big
and you really do want them on the heap
that brings us to number 23 not using
unique pointer and shared pointer to do
your heap allocations
what happens if an exception gets thrown
in the middle here then these deletes
never occur and the memory is leaked
when you want to make sure that a
resource is cleaned up you need to put
that cleanup code in a destructor so why
don't we make a class that holds a
pointer and then in its destructor it
deletes that pointer
well that's exactly what unique pointer
does
you can give it a heap allocated pointer
and when it goes out of scope it deletes
it
a shared pointer on the other hand uses
a reference counting scheme similar to
what you might have in a language like
python
when the reference count hits 0 as the
last shared pointer goes out of scope
that shared pointer is in charge of the
deletion
this scheme is much more expensive
because reference incrementing and
decrementing have to be done atomically
that brings us to number 24 constructing
a unique or shared pointer directly
instead of using make unique or make
shared
make unique and make shared will pass
your arguments directly to the
constructor of your object
25
any use of new or delete
there's no reason to rewrite
functionality that already exists
here i'm trying to manage the memory of
some resource and then delete it when
it's done
that's already what a unique pointer
does
don't try to couple the purpose of your
class to the idea of ownership of an
object that's a completely separate
issue
unique pointer and shared pointer
together cover pretty much every valid
use of new or delete
number 26 any kind of attempt to do
manual resource management
this is pretty much the same story as
with new and delete
if you find yourself manually freeing
closing or releasing any kind of
resource then look to see if there's a
class that does that automatically
by the way this idea of having resources
automatically close in a destructor is
called raii
it stands for resource acquisition is
initialization but it really has more to
do with ensuring that resources are
released upon destruction
number 27 thinking that raw pointers are
somehow bad
here's a basic max function
this function is just reading from the
pointers it doesn't care at all who's in
charge of deleting them
if your function doesn't have anything
to do with the ownership of the objects
in question then there's no need to use
smart pointers the convention is that
raw pointers don't own what they're
pointing to
and this should not be confused with the
const-ness of the pointer this add
function adds the source into the
destination
but this function is not in charge of
the lifetime of either of the pointers
of course if you're interoperating with
c code c code is not going to share this
convention
a c function might very well return some
heap allocated memory to you that it
expects you to delete
be careful though if you've got a
pointer with malik you need to delete it
with free you can't just let
uniquepointer try to delete it with the
built-in delete
you can still use a unique pointer
though you just need to define your own
deleter which uses free number 28
returning a shared pointer when you
aren't sure the object is going to be
shared
if a caller got a unique pointer it's
cheap and easy to convert it into a
shared pointer if that's what they
really need
they could even directly assign the
unique pointer return value to a shared
pointer
but if you return to them a shared
pointer in the first place the damage
would already be done if all they really
wanted was a unique pointer
number 29 thinking that shared pointer
is thread safe
the reference counting part of the
shared pointer is thread safe here i
make a shared resource and pass it off
to two worker threads
because the reference counting is thread
safe there's no danger that the object
will fail to be deleted or be deleted
twice
however it's only the reference counting
part that's atomic
this part here where we're accessing the
x variable and the resource is not
atomic and there's no locks
this is a plain old data race of two
threads trying to modify the same memory
if you want to fix the data race you
need to fix it the way you'd fix any
other data race
and 30 confusing a const pointer with a
pointer to const
the concept of a const pointer versus
pointer to const is pretty simple
but a lot of newbies struggle to
remember how to tell the difference
between them syntactically
the rule is that const applies to
whatever is immediately to its left
unless it's the leftmost thing in which
it applies to the thing to its right
so here the cost applies to the int not
to the pointer
here the cons applies to the int not to
the pointer
and here con supplies to the pointer not
to the int
number 31 ignoring compiler warnings
ignoring them or turning them off very
frequently leads to undefined behavior
i hope you enjoyed my list of newbie c
plus habits
as always thank you to my patrons and
donors for supporting me
see you next time