c Advanced Programming

Front row tip: This article is not suitable for beginners

Replace #define

Replace with const

If you are familiar with the compilation principle, you should know what a symbol table is. If you don't understand it, you can turn it over.

We all know that #define will be processed in the precompiling stage (usually), so if an error occurs during program compilation, the error message we see is often the content behind #define, and its own symbol will not be displayed in the error prompt column, which is easy to cause confusion. Therefore, we often replace define with const; There are two special cases:

  1. Pointer - constant pointer or direct string
    const char* const p="I love wqm"
    
    or
    
    const std::string p("I love wqm")
    
  2. Class member variable - constant static member variable: initialization can be given in the declaration, and the definition can take nothing. The old compiler does not support this operation, and can only assign values in the definition,
    This will lead to a problem: if we want to use this constant when defining the num array, we define it outside, and the compiler cannot pass because it wants to insist on knowing the value of num.
    class test
    {
        static const num=5;
        int nums[num]
    }
    const int test::num;

Replace with enum

In the old compiler, if internal defined values are not supported, we can use enum instead. It is different from const in two points:

1. In fact, enum is more like define because you can't get an address. If you don't want a pointer to this object, enum is recommended,
2. Do not allocate more space. A compiler that is not good enough may allocate additional space to const

class test
{
    enum{num=5};
    int nums[num]
}

Replace with inline

A great advantage of macro definition is that it can define simple functions, but it will not incur additional overhead caused by function calls, such as additional stack calls, such as this:

#define MY_MAX(a,b) f((a)>(b)?(a):(b))

But in fact, this writing method is very silly, because we must add parentheses to all the facts because of the symbol priority, otherwise the operation may be performed in advance with the operators around the macro, resulting in unsatisfactory results. Another example is this calling method:

int a=5,b=0;
MY_MAX(++a,b);
My_MAX(++a,b+10);

If the number passed in is itself a number with operators, it is likely that the result will be contrary to our wishes.

We can replace the simple function with the template inline function

template<typename T>
inline void myMax(const T& a,const T& b)
{
    f(a>b?a:b);
}

This method does not need to worry about multiple evaluations. Another advantage is that it can be implemented in a class and has a scope, while define cannot define a scope.

Use constexpr and const as much as possible

The c++14 version further expands the use of constexpr. It requires that both the input and output are const, which are different. Const can represent literal constants or read-only, while constexpr can only represent literal constants. If constexpr is used as a function prefix, the compiler can boldly optimize.

It should be emphasized that the calculation timing of constant expression and non constant expression is different. Non constant expression can only calculate the result in the program running stage, but the calculation of constant expression often occurs in the compilation stage of the program, which can greatly improve the execution efficiency of the program, because the expression only needs to be calculated once in the compilation stage, It saves the time that needs to be calculated every time the program runs.

However, it should be noted that not all functions plus can be treated as literal constants. If the parameters passed in are not literal constants but only read-only, they will still be treated as ordinary functions. At this time, constexpr only recommends compiler optimization, but it does not necessarily optimize. For example, the following is compiled at runtime.

using namespace std;
constexpr int f(const int x)
{
    return x+10;
}
int main()
{
    int i=cin.get();
    cout<<f(i)<<endl;
}

Const is used in ordinary functions to indicate read-only when the return value is const. Therefore, if there are operations such as assignment, it is meaningless to avoid bug s. Even if a value is returned, it is a copy instead of a value. This kind of return will be reported as an error by the compiler, such as:

class num 
{
public:
    int num = 0;
    int get() 
    {
        return this->num;
    }
};
int main()
{
    num n1;
    n1.num = 1;
    n1.get() = 2;
    system("pause");
}

If you const overload the return value of a function inside the class, it will have different effects:

using namespace std;
class nums 
{
public:
    int num = 0;
    nums(int number) :num(number) {};
    int get() 
    {
        cout << "not const" << endl;
        return this->num;
    }
    const int get() const
    {
        cout << "const" << endl;
        return this->num;
    }
};
int main()
{
    nums n1(1);
    const nums n2(2);
    n1.get();
    n2.get();
    system("pause");
}

The output results are as follows, so we can formulate different functions according to whether it is const or not.

  If we do not write const version, the compilation with const version cannot pass

If the two contents are the same, then the simple solution is like this. On the contrary, it is not recommended

using namespace std;
class nums 
{
public:
    int num = 0;
    nums(int number) :num(number) {};
    int get() 
    {
        auto&& temp = static_cast<const nums>(*this).get();
        return const_cast<int&>(temp);
    }
    const int get() const
    {
        cout << "const" << endl;
        return this->num;
    }
};
int main()
{
    nums n1(1);
    const nums n2(2);
    n1.get();
    n2.get();
    system("pause");
}

It is also worth pointing out that if you want to change some member objects for a const member function, you can add mutable to the definition to eliminate the constraints on this variable, such as this.

using namespace std;
class nums 
{
public:
    int num = 0;
    mutable int num2 = 0;
    nums(int number) :num(number) {};
    int get() 
    {
        auto&& temp = static_cast<const nums>(*this).get();
        return const_cast<int&>(temp);
    }
    const int get() const
    {
        cout << "const" << endl;
        num2 += 1;
        return this->num;
    }
};
int main()
{
    nums n1(1);
    const nums n2(2);
    n1.get();
    n2.get();
    system("pause");
}

Initialization order of static parameters of different files

First, explain that this order has no solution. For example, we define a Class A in file a, and then another class B in file B. our class B needs a static parameter of class A to initialize. This parameter is called no_local static param, and the compiler itself cannot guarantee that this thing is initialized before we define the class, so we need to use an amount function as a layer of encapsulation:

A& getA()
{
    static A a;
    return a;
}

If multithreading is involved, we need to consider the rearrangement problem. We need to add fence to prevent assembly rearrangement, so as to generate multiple objects. It's a bit like the singleton pattern of design pattern. See my blog in the design pattern section for specific solutions.

Tags: C++ Back-end

Posted on Fri, 29 Oct 2021 21:40:59 -0400 by winggundamth