Developing Effective Code with C++

I like C++. If you would like to learn object-oriented programming and know what you program, I would recommend C++. There is a lot you can do with C++, and it would be even better if you do it effectively. In this article, I explain how to use C++ more effectively.
It would be more accurate to say that C++ supports OOP, and mentioning the concept of a federation of languages at this point is not wrong.
C++ is a language that supports the coexistence of many paradigms. It is a multiparadigm language, supporting procedural, functional, generic, metaprogramming, and object-oriented programming paradigms. We can explain these concepts in another blog post. As this is our first evaluation, let’s start with the first rule:
1- We need to know which type of paradigm we are moving forward to develop effective C++ code.
When developing the C++ code, we should prefer the compiler to the precompiler. What does this mean? We should use const, enums, and inline keywords instead of define:
#define PI 3.1416
The compilers may never notice the symbolic name PI, or the precompilers can delete it before it even reaches the compiler. As a result, the PI may never join the symbol table. When “define” is used, the compiler may never find the error related to PI, and this makes debugging very hard, exceptionally if “define” is in a header file we didn’t write.
The solution to this is straightforward; we need to replace the sentence as below:
const double Pi = 3.1416
Then our rule is;
2- We should always choose the compiler over the precompiler.
We see constant “const” in C++ codes. Frankly, it used to make me a little uncomfortable because I was used to writing loosely-coupled code. Of course, this was causing errors all the time. You should always use “const” if you don’t want to change a variable. It is genuinely an encapsulation process.
I don’t want to go into detail about what “const” is, but let’s check a good example applied to operators:
class Number {....}; const Number operator* (const Number& lhs, const Number& rhs);
So, that must be our code. Our code does not allow such an attempt:
Number a,b,c; (a * b) = c;
If the result of the operator* wasn’t a “const” number, this process could be successful, but in this case, we are preventing that error.
Let’s look at our example that is much simpler and shows encapsulation clearly:
class Number { int raw; int foo; public: int getRaw () const {return raw;} };
getRaw is a member function. If you pay attention, there is a “const” statement after the parentheses. It means that the member function (getRaw) does not change the value of any member variable (raw or foo). So when we write a sentence like “foo = 2” in getRaw, the compiler gives an error.
3- Const limits us; we should use “const” everywhere necessary.
One of my favorite aspects of C++ is that you get to decide how to use the resources. However, this leads to other problems that result in some languages gaining popularity over the others — Garbage Collector.
Maybe you’ve heard of a concept called “RAII“(Resource Acquisition Is Initialization), which very basically says, finish what you have started. The finalization should also be done if there is an initialization.
When an object is created, the constructor is called, and when it is killed, the destructor is called. If it is dying, then there is no problem. But there are some places in heap management that we should stop and look again. For example, if a pointer is created with an object, the pointer should especially be deleted when the destructor is called. Otherwise, we’re inviting the problem called a memory leak. I explain in detail how to create our RAII classes in another blog post. I’m going to talk about a few of the fastest resource management tools available.
If you don’t want to worry about whether the pointer is dead or not, using std: shared_ptr or auto_ptr is the best option you have. The difference between the two is that when you use the auto_ptr, the ownership of a pointer change to auto_ptr, and the pointer losing ownership becomes null/disabled. The new auto_ptr disables the previous auto_ptr if it is owned by another auto_ptr. It goes on like this. shared_ptr allows multiple pointers to access the same source; however, when there is no shared_ptr left trying to access the resource, shared_ptr dies.
Examples:
{ std::auto_ptr<std::string> string1 (new std::string("burada bir string var")); //string1 new.. ile başlayan nesneye point ediyor. std::auto_ptr<std::string> string2 (string1); //string2 şimdi "burada bir string var" string'ine point ediyor ama dikkat string1 şimdi null string1 = string2; // şimdi tekrar string1 point ediyor string2 ise null }
Above, we see how auto_ptr works, and let’s look at shared_ptr:
{ std::shared_ptr<std::string> string1 (new string("burada yine bir string var")); // string1 "burada yine bir string var" string'ine point ediyor. std::shared_ptr<std::string> string2 (string1); //hem string1 hem de string2 pointerları "burada yine bir string var" stringine point ediyor. string1 = string2; // :) hiç bir değişiklik yok. aynı tas aynı hamam } // string1 ve string2 pointerları destroy edildi ve point ettikleri nesne de otomatik olarak silindi.
4- Using shared_ptr or auto_ptr based on our purpose allows us to use the resources correctly.