Best practices
You'll likely see some things in my examples that you either you don't understand or don't like. This section is dedicated to addressing why I make some of the choices I make.
As for style, I sometimes conform to Google's style guide, but don't always hit the mark.
Don't import the entire std
namespace
When first learning C++, many tend to tack using namespace std;
to the top of their code as a rule. This should be avoided.
The following example shows an ambiguity in resolving a function call because two namespaces have a function with the same name.
#include <iostream>
namespace foo {
void print() {
std::cout << "Hello from foo!" << std::endl;
}
}
namespace bar {
void print() {
std::cout << "Hello from bar!" << std::endl;
}
}
using namespace foo;
using namespace bar;
int main() {
print(); // ambiguous - foo::print() or bar::print()?
return 0;
}
This can be resolved by specifying which print
to call - foo::print
or bar::print
.
If you find that writing foo::print
repeatedly is too cumbersome, you can add using foo::print;
, which will allow you to access print
by name instead of foo::print
. Typically when this is done, the scope of the using
declaration is minimized. This can be seen in the following example:
#include <iostream>
int main() {
using std::cout;
using std::endl;
cout << "Hello, World!" << endl;
return 0;
}
Personally, I think this looks clunky and would prefer to type a few extra characters than to minimize the scope and risk re-typing the entire using
declaration again later, like in the following:
#include <iostream>
int main() {
{
using std::cout;
using std::endl;
cout << "Hello, World!" << endl;
}
{
using std::cout;
using std::endl;
cout << "Hello, World (again)" << endl;
}
return 0;
}
Use initialization lists
There are two way to initialize members in C++:
The first is by assigning the variable in the body of the constructor:
class Foo {
public:
Foo() { bar = 42; }
private:
int bar;
};
The other is by using initialization lists:
class Foo {
public:
Foo(): bar(42) {} // Call `int` constructor with value 42.
private:
int bar;
};
I prefer the latter for several reasons.
You won't need to have a large constructor body
This is completely a matter of opinion, but I think large constructor bodies are ugly. I'm of the opinion that constructors should have as little code as possible - initializing members and maybe calling an init
method. By using an initialization list, the body will be much smaller. This makes the constructor much more readable and maintainable.
It's less efficient to use the assignment method
By using the assignment method, copies are constructed. Then when the assignment is made, the copy assignment operator is called to initialize the member. If the right-hand side of the assignment operator is expensive, making a copy is definitely not what you want. There's a way to circumvent this (i.e., prevent extra copies) that we will talk about later, but it's truly better to just use the initialization list as a rule.
Use std::size_t
for sizes.
I'll often see something like the following:
int size = my_obj.size();
This is not good. Here's why:
The C++ Standard makes no guarantees about the size of int
. In fact, it makes no guarantees about the size of any integral types.
It guarantees this:
1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
It also guarantees that std::size_t
is capable of expressing the size of any type in C++ lest it be ill-formed.
Because int
is smaller than std::size_t
, it will overflow faster if the size to be represented is too large.
If sizeof int
is 4 bytes, it can address up to 32 bits: , which is the largest representable integer in a 32-bit system.
If my_obj.size() == 2147483647
, there could be real trouble in the following example:
#include <iostream>
int main() {
// Assume my_obj.size() == 2147483647
int size = my_obj.size();
size++;
std::cout << size << std::endl;
return 0;
}
We expect to see 2147483648, but instead we see -2147483648. This is because of integer overflow. This is undefined behavior in the C++ Standard, which is a huge no-no.
The moral of the story is that you should use std::size_t
to express sizes. It's guaranteed to represent any size and it just makes sense.