effective c++ 27 Do as few transformation actions as possible

effective c++ 27 do as little transformation as possible

This section mainly discusses issues related to mandatory type conversion, and points out that mandatory type conversion should be used as little as possible in normal development, which is not conducive to operating efficiency, and sometimes may cause unexpected problems.

Analysis

In the C language, the mandatory type conversion format is as follows:

(T)expression //Transform expression to T

In C++, four new types of transformations are provided:

const_cast<T>(expression)

dynamic_cast<T>(expression)

reinterpret_cast<T>(expression)

static_cast<T>(expression)

We use two cases to analyze why we should do as little transformation as possible.

The first case is a problem caused by improper use of the transition action.

Inside the virtual function onResize of SpecialWindow, we want to call the default implementation of onResize inside the Window class first.

So here we use static_cast(*this).onResize(); to do transformation, transform into Window class and then call onResize method, will this method work?

#include <iostream>
class Window
{<!-- -->
public:
virtual void onResize() {<!-- -->
        a_ = 1;
    }
virtual ~Window() {<!-- -->}
    int getA() const
    {<!-- -->
        return a_;
    }
private:
    int a_{<!-- -->0};
};

class SpecialWindow : public Window
{<!-- -->
public:
virtual void onResize()
{<!-- -->
static_cast<Window>(*this).onResize();
}
private:
    int b_{<!-- -->0};
};

int main()
{<!-- -->
SpecialWindow specialWindow;
specialWindow.onResize();
    std::cout << specialWindow. getA() << std::endl;
}

output:

0

have a try

From the output results, we can see that although the onResize method of the parent class was called after the transformation, the internal modification of the value of a_ did not take effect, because we finally printed the getA method of specialWindow and found that the value of a_ was still 0, not 1.

What is the problem?

static_cast(*this).onResize() does not operate on *this itself, but a copy.

static_cast(*this) is a temporary copy of Window.

The above statement is a bit like the following statement, the last modification is the value in the temporary object, not (*this).

Window tmp = static_cast<Window>(*this);
tmp.onResize();

It is also very simple to modify, that is, remove the mandatory type conversion and use Window::onResize.

#include <iostream>
class Window
{<!-- -->
public:
virtual void onResize() {<!-- -->
        a_ = 1;
    }
virtual ~Window() {<!-- -->}
    int getA() const
    {<!-- -->
        return a_;
    }
private:
    int a_{<!-- -->0};
};

class SpecialWindow : public Window
{<!-- -->
public:
virtual void onResize()
{<!-- -->
Window::onResize();
}
private:
    int b_{<!-- -->0};
};

int main()
{<!-- -->
SpecialWindow specialWindow;
specialWindow.onResize();
    std::cout << specialWindow. getA() << std::endl;
}

Next, let’s look at the second case. The second case is that the transformation operation will bring additional performance overhead.

Here we store the pointer of the parent class in the vector, but we want to call the blink method of the subclass, so we need to use dynamic_cast to cast it to the pointer of the subclass object, and then call the blink method.

#include <iostream>
#include <memory>
#include <vector>
class Window
{<!-- -->
public:
    virtual ~Window() = default;
};

class SpecialWindow : public Window
{<!-- -->
public:
void blink()
{<!-- -->
        std::cout << "blink" << std::endl;
int x = 7;
}
};

int main()
{<!-- -->
    using VPW = std::vector<std::shared_ptr<Window>>;
    VPW winPtrs;
    std::shared_ptr<Window> ptr1 = std::make_shared<SpecialWindow>();
    std::shared_ptr<Window> ptr2 = std::make_shared<SpecialWindow>();
    winPtrs.push_back(ptr1);
    winPtrs.push_back(ptr2);
    for(VPW::iterator iter = winPtrs. begin(); iter != winPtrs. end(); + + iter)
    {<!-- -->
        Window* w = iter->get();
        SpecialWindow* psw = dynamic_cast<SpecialWindow*>(iter->get());
        if (psw)
            psw->blink();
    }
}

We know that dynamic_cast has performance overhead, and dynamic_cast needs to call strcmp for string comparison. Performing a dynamic_cast on an object in a four-level deep single-inheritance hierarchy could consume as many as four strcmp calls.

So how to optimize it?

One optimization method is not to store the share_ptr of the Window type in the vector, but to store the share_ptr of the SpecialWindow type.

#include <iostream>
#include <memory>
#include <vector>
class Window
{<!-- -->
public:
    virtual ~Window() = default;
};

class SpecialWindow : public Window
{<!-- -->
public:
void blink()
{<!-- -->
        std::cout << "blink" << std::endl;
int x = 7;
}
};

int main()
{<!-- -->
    using VPW = std::vector<std::shared_ptr<SpecialWindow>>;
    VPW winPtrs;
    std::shared_ptr<SpecialWindow> ptr1 = std::make_shared<SpecialWindow>();
    std::shared_ptr<SpecialWindow> ptr2 = std::make_shared<SpecialWindow>();
    winPtrs.push_back(ptr1);
    winPtrs.push_back(ptr2);
    for(VPW::iterator iter = winPtrs. begin(); iter != winPtrs. end(); + + iter)
    {<!-- -->
        (*iter)->blink();
    }
}

Another way is to modify blink to a virtual function:

#include <iostream>
#include <memory>
#include <vector>
class Window
{<!-- -->
public:
    virtual ~Window() = default;
    virtual void blink(){<!-- -->};
};

class SpecialWindow : public Window
{<!-- -->
public:
virtual void blink() override
{<!-- -->
        std::cout << "blink" << std::endl;
int x = 7;
}
};

int main()
{<!-- -->
    using VPW = std::vector<std::shared_ptr<Window>>;
    VPW winPtrs;
    std::shared_ptr<Window> ptr1 = std::make_shared<SpecialWindow>();
    std::shared_ptr<Window> ptr2 = std::make_shared<SpecialWindow>();
    winPtrs.push_back(ptr1);
    winPtrs.push_back(ptr2);
    for(VPW::iterator iter = winPtrs. begin(); iter != winPtrs. end(); + + iter)
    {<!-- -->
        (*iter)->blink();
    }
}

Of course, the above two methods are just two ways of thinking, and it does not mean that they must be done. When you really need to forcibly turn, go forcibly.

In one sentence, it is to reduce the transformation as much as possible, but it does not mean to completely eliminate the transformation. This point must be kept in mind, and the transformation must be carried out when necessary.

Summary

  • Avoid casts if you can, especially avoid dynamic_cast in efficiency-conscious code. If there are individual designs that require transition actions, try to develop alternative designs that do not require transitions.
  • If the cast is necessary, try to hide it behind a function. Clients can then call this function without putting the cast into their own code.
  • It is better to use C++ style new-style transformation than old-style transformation. The former is easy to identify, and it also has more classified functions.