C++ and C are the only high-level programming languages that give you complete freedom and permission to work the way you see fit.
Lately, we’ve been hearing, or reading, the mantra a lot:
- C and C++ are insecure programming languages.
It is important to point out, that this “insecurity” is a fault of the programmer and not of the programming language. The truth is that almost everything we use is written in C and C++ and by programmers who know how to use these tools.
Therefore, you can see that writing good code in C++ will depend exclusively on the programmer. And writing well in C++ is not such a difficult task, just dedicate yourself.
In this article we are going to know 20 basic tips to write better in C++!
using namespace
Several libraries tend to repeat names, the use of namespace
is precisely to distinguish where you are using it. When you make this global, it generates conflicts when there are same names. Not to mention that your code is more readable when someone else reads it or even you, it’s good to explicitly know the origin of that function or class.
Avoid using using namespace std;
or any other namespace.
using namespace std;
int main(){
cout << "Hello, World!" << '\n';
}
Make it a point to use directly with scope resolution!
int main(){
std::cout << "Hello World!" << '\n';
}
If you need to use it for specific words, such as cout, string and others, specify only them, for example:
using std::cout;
using std::string;
int main(){
string hello = "Hello, World!";
cout << hello << '\n';
}
std::endl
std::endl
was created to make line breaks portable between different operating systems. For example, on UNIX-like systems (BSD, macOS, GNU/Linux) to make a line break just use '\n'
, but on DOS-like systems (Windows) it is used like this: "\r\n"
.
So std::endl
directed the flow according to the operating system. But, nowadays the current versions of the compilers already do this. Then there is no need anymore! Using std::endl
adds one more useless processing: std::flush
. Only use '\n'
, enclosed in single quotes, or "Hello\n"
, when used in conjunction with strings.
Instead:
std::cout << "Hello World!" << std::endl;
std::cout << std::endl;
std::cout << var << '\n';
Use like this:
std::cout << "Hello World!\n";
std::cout << '\n';
std::cout << var << '\n';
In the past, C/C++ programmers used to use conditions and loops without the use of braces { }
this caused semantic problems in a lot of code. To avoid these problems and make your code more readable, use curly braces, for example:
Instead:
int main(){
for( int i{}; i < 4; ++i)
std::cout << i << '\n';
if ( 1 == 1 )
std::cout << "One is himself =)\n";
else
std::cout << "He is not himself :(\n";
}
Use like this:
int main(){
for( int i{}; i < 4; ++i){
std::cout << i << '\n';
}
if ( 1 == 1 ){
std::cout << "One is himself =)\n";
}else{
std::cout << "He is not himself :(\n";
}
}
: ()
In addition to your code gaining performance, your code is also leaner and easier to understand.
Instead:
class Vector2 {
int x, y;
public:
Vector2(int x, int y){
this->x = x;
this->y = y;
}
};
Use like this:
class Vector2 {
public:
int x, y;
Vector2(int x, int y): x(x), y(y){ }
};
{}
to initialization, to =
This makes the compiler’s job easier and avoids failing to initialize a member/variable.
Instead:
int x = 0;
int y = 42;
std::string hello = "";
std::string world = "World!";
Use like this:
int x {0};
int y {42};
std::string hello {};
std::string world {"World!"};
std::size_t
Several times we need to use some member or variable to make comparison. When this is done with elements of a vector, the compiler will issue several warnings because their comparison is invalid. This can even result in data loss. So, get used to using the size_t
of std
, for example:
Instead:
int y {42};
for(int i{}; i < v.size(); ++i){}
Use like this:
std::size_t and {42};
for(std::size_t i{}; i < v.size(); ++i){}
.hpp
and .cpp
for your file extensionsThe use of .cc
and .h
deviates from most standards, especially .h
which is used for C language.
Instead:
main.cc entity.h entity.C common.h lib.h
Use like this:
main.cpp entity.hpp entity.cpp common.hpp lib.hpp
&
) and pointers(*
)And especially, when also possible, the use of
const
Instead:
void print(std::string str){}
std::vector<int> mylist(std::vector<int> list){
return list;
}
std::string use_str(std::string mystr){
return mystr;
}
Use like this:
void print(const std::string &str){}
std::vector<int> mylist(const std::vector<int> & list){
return list;
}
std::string use_str(const std::string &mystr){
return mystr;
}
This avoids memory leaks and unexpected errors, there are others, however the most modern is shared_ptr
to create the pointer and make_shared
to allocate memory space or share characteristics of a pointer.
Instead:
MyClass * mc = new MyClass();
Other * ot = new Other;
delete mc;
delete ot;
Use like this:
std::shared_ptr<MyClass> mc =
std::make_shared<MyClass>();
auto ot = std::make_shared<Other>();
std::array
or std::vector
Both ensure contiguous memory layout of objects and can (and should) completely replace the use of C-style arrays for many of the reasons listed for not using simple pointers.
Instead:
int arr[10] = {1,2,3,4,5,6,7,8,9};
std::string fruits[4] = {"Apple", "Orange", "Banana", "Cherry"};
Use like this:
std::array<int, 10> arr {1,2,3,4,5,6,7,8,9};
std::vector<const char*> fruits {"Apple", "Orange", "Banana", "Cherry"};
static_cast<>
, dynamic_cast<>
…C++ style conversion allows more compiler checks and is considerably safer. Also, the C++ conversion style is more visible and has the ability to search.
Instead:
int i = (int) z;
a = (char)x;
y = (int)b;
Use like this:
int i = static_cast<int>(z);
a = static_cast<char>(x);
y = static_cast<int>(b);
const
Macros is a practice widely used by old programmers of the C language, but in C++ it is a bad idea, even for C itself, among several evils, they include:
-g
, -Wall
, -Werror
, …Instead:
#define PI 3.14
#define printf std::cout
#define OUTPUT(x) myfunction(x)
Use like this:
const double PI{3.14};
const typedef std::vector<int> VEC;
FILE * file
or DIR
from C, use std::filesystem
It was implemented starting with C++17 and is also critical for portability. Not to mention that it has several non-member functions, which facilitate the manipulation of strings separately, especially when you work with files and directories on different disks.
Instead:
bool exists( const char * home ){
DIR * path = opendir( home );
if( path ){
closedir(path);
return true;
}else{
return false;
}
}
int main(){
char * home = getenv("HOME");
const char * dir = "/Downloads";
strcat(home,dir);
exists(home) ? printf("Yes") : printf("Not");
return 0;
}
Use like this:
bool exists( const std::string &path){
return std::filesystem::exists(path);
}
int main(){
std::string home = getenv("HOME");
home = home + "/";
std::cout << ( exists(home + "Downloads")
? "Yes" : "Not") << '\n';
return 0;
}
template
instead of creating multiple classes that do the same thing, make use of the using
keyword for alias of names.Instead:
class Vector2i {
public:
int x, y;
Vector2i(int x, int y): x(x), y(y){ }
};
class Vector2f {
public:
float x, y;
Vector2f(float x, float y): x(x), y(y){ }
};
class Vector2u {
public:
unsigned x, y;
Vector2u(unsigned x, unsigned y): x(x), y(y){ }
};
Use like this:
template<class T>
class Vector2 {
public:
Tx,y;
Vector2(T x, T y): x(x), y(y){ }
};
using Vector2i = Vector2<int>;
using Vector2f = Vector2<float>;
using Vector2u = Vector2<unsigned>;
++i
than i++
It increments before printing, this seems to be negligible, but I’ve had unexpected results .
Instead:
std::cout << "Points: " << i++ << '\n';
Use like this:
std::cout << "Points: " << ++i << '\n';
\n
inside double quotes when using just it, remember it is a char
and not a string
.Using double quotes makes the compiler interpret it as a const char *
, with single quotes it is really what a char
is.
Instead:
std::cout << hello << "\n";
Use like this:
std::cout << hello << '\n';
lambda
Lambdas solve a problem of readability, expressiveness and practicality. In addition to making your code cleaner and LIKE A BOSS!
Instead:
int sum(int x, int y){
return x + y;
}
int main( int argc , char **argv ){
int n = sum(5, 4);
std::cout << n << '\n';
return 0;
}
Use like this:
int n = [](int x, int y){
return x + y;
}(5, 4);
std::cout << n << '\n';
for
loops over traditionalThis is a modern practice and don’t forget to always assign reference to the index!
Instead:
std::unordered_map<const char*, uint32_t> levels = {
{"Level Map", 39},
{"Hard Boss", 112},
{"Very Easy", 42},
{"End", 458}
};
for( auto i = levels.begin(); i != levels.end(); ++i ){
std::cout << i->first << " → " << i->second << '\n';
}
Use like this:
std::unordered_map<const char*, uint32_t> levels = {
{"Level Map", 39},
{"Hard Boss", 112},
{"Very Easy", 42},
{"End", 458}
};
for( auto&[i, j] : levels){
std::cout << i << " → " << j << '\n';
}
Programmers who came from other programming languages also tend to inherit terms like: Methods and Properties in Object Oriented.
In C++ there are no methods and properties, but MEMBERS and MEMBER FUNCTIONS .
Using these terms in your software documentation confuses C++ programmers who are not used to it.
Whenever possible use additional tools to debug, optimize and test your code, such as:
If you want to see a video based on this article, follow the same below.
Remembering that the video is in Portuguese, but you can use Yotube’s automatic translation for your language. Without also saying that the code is universal, even without translation it is easily understandable.