This article records some of the problems and optimization methods of C++ in these years’ projects. It is important to note that there is no one-size-fits-all method for code optimization, and you can only see what you can do, and you have to avoid problems such as premature optimization, code optimization must be done in the middle and late stages, and don’t optimize for the sake of optimization.

const and const &

The const modifier is not used when receiving a return value or declaring a local read-only variable. the purpose of const is not just to be read-only, but more so that the compiler can provide optimizations here.

1
2
QRect rect = m_displayInter->primaryRawRect();
qreal scale = qApp->primaryScreen()->devicePixelRatio();

In both lines of the example, both react and scale are unmodified within the current function and should not be modified, const needs to be added to modify read-only, and QRect should use & to reduce the additional impact of memory copying.

Type forced conversion

In parts of the code, you can often see C style code strong cast, and should use static_cast, dynamic_cast and reinterpret_cast depending on the case.

static_cast is the more used cast, often used to convert between derived and base classes. dynamic_cast is also used to convert between derived and base classes, if type T is a pointer type, if the conversion fails, it returns a null pointer of type T. If when T is a reference type, it will throw an exception and return std::bad_cast. The reinterpret_cast does not do the actual conversion, but only checks at compile time and reports an error if the cast conversion cannot be done.

Excessive nesting

Too much nesting will seriously affect the code reading, often only if passed will enter the execution of the situation, this situation should be modified to do not continue to execute if not passed, or arrange a reasonable if to restrict the condition before.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void BluetoothWorker::setAdapterPowered(const Adapter *adapter, const bool &powered)
{
    QDBusObjectPath path(adapter->id());
    //关闭蓝牙之前删除历史蓝牙设备列表,确保完全是删除后再设置开关
    if (!powered) {
        QDBusPendingCall call = m_bluetoothInter->ClearUnpairedDevice();
        QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
        connect(watcher, &QDBusPendingCallWatcher::finished, [ = ] {
            if (!call.isError()) {
                QDBusPendingCall adapterPoweredOffCall  = m_bluetoothInter->SetAdapterPowered(path, false);
                QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(adapterPoweredOffCall, this);
                connect(watcher, &QDBusPendingCallWatcher::finished, [this, adapterPoweredOffCall, adapter] {
                    if (!adapterPoweredOffCall.isError()) {
                        setAdapterDiscoverable(adapter->id());
                    } else {
                        qWarning() << adapterPoweredOffCall.error().message();
                    }
                });
            } else {
                qWarning() << call.error().message();
            }
        });
    } else {
        QDBusPendingCall adapterPoweredOnCall  = m_bluetoothInter->SetAdapterPowered(path, true);
        QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(adapterPoweredOnCall, this);
        connect(watcher, &QDBusPendingCallWatcher::finished, [this, adapterPoweredOnCall, adapter] {
            if (!adapterPoweredOnCall.isError()) {
                setAdapterDiscoverable(adapter->id());
            } else {
                qWarning() << adapterPoweredOnCall.error().message();
            }
        });
    }
}

The code here can actually be optimized, we can get a certain QDBusPendingCall through a ternary expression, so that we can use a QDBusPendingCallWatcher object, and then extract the original lambda content into other functions, and run the corresponding function in the new lambda using the same ternary expression The advantage of splitting the code in this way is that the order of reading the code will be the same as the order of execution. Branching judgments are not too friendly to both machines and humans, especially when there are long code segments within the judgment body, finding the else segment is not an easy task, and the code readability is improved by reducing the if else block. Also the code of the same action should be extracted to the common area so that future modifications will not find that not all the places are modified.

loop

Avoid using arrays to access elements and use iterators to unify the loop approach.

I have noticed that in some cases, people define static variables directly inside for loops, this way of use requires attention, static variables will always exist, but most of the data that needs to be saved inside for loops are member variables, otherwise the memory space will never be freed and there is a waste of memory.

And often the problem is that foreach macros and for are mixed and not used uniformly in the syntax.

The way I recommend is for + iterator, if it’s a simple traversal, just use the native foreach syntax.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
std::list<int> list{ 1, 2, 3, 4};

// 原生foreach语法,推荐只读遍历使用
for (int item: list) {
    ...
}

// for+迭代器,只读遍历
for (auto it = list.cbegin(); it != list.cend(); ++it) {
    // it是迭代器对象,需要解引用使用。
    *it
}

// for+迭代器方式,推荐需要修改容器的长度使用
for (auto it = list.begin(); it != list.end();) {
    // 注意,如果要移除某个元素,需要手动下一步
    if (*it == 2) {
        it = list.erase(it);
    }
    else {
        ++it;
    }
}

Memory Leak

We often encounter scenarios where pointers are saved using containers, but when objects are destructured or containers are emptied, sometimes we forget to delete internal objects, or delete objects that should not be deleted. The handling of data should maintain the RAII principle and avoid using bare pointers directly, but save the pointers through smart pointers. When the last object no longer holds a smart pointer object, the smart pointer will delete the held object and complete the memory release.

Types of Smart Pointers

There are three types of smart pointers: exclusive pointer unique_ptr, shared pointer shared_ptr and weak reference pointer week_ptr.

Exclusive Pointer

The exclusive pointer std::unique_ptr prevents objects from being transferred to other objects. If an object holds unique_ptr, the ptr is not allowed to be transferred to other objects, but control can be transferred using std::move, note that this is not the same as a normal transfer, what unique_ptr prohibits is copying. But there is no prohibition on move, we can transfer control to it. unique_ptr guarantees that only one smart pointer holds the object.

1
std::unique_ptr<T> p1 = std::move(ptr);

Shared Pointers

Shared pointer std::shared_ptr as the name suggests is used as a shared, and exclusive pointer is different, it supports copying, internal reference counting to maintain the object life cycle, when no object holds a shared pointer, it means that no object can access the internal object, it is safe to delete the object and free memory.

Weak reference pointers

The weak reference pointer std::week_ptr is a solution proposed to avoid two shared pointers holding each other causing the reference count to never go to zero, resulting in memory never being freed, specifically the weak reference pointer will not cause the reference count to increase, but week_ptr also does not support copying and must be converted to a shared pointer std::shared_ptr. .

Optimization of judgment conditions

For constant judgments, try to use macros or define static constants to avoid using numbers or characters directly.

Sort

I found that many people always use bubbling algorithm when they need to sort, I introduce a few more convenient sorting methods.

Using std::sort

The C++ standard library provides the std::sort method to facilitate sorting. It has three arguments, the first argument is the container’s begin iterator, the second argument is the end iterator, and the third argument receives a function that returns a value of type bool, which is used to implement a manually controlled sorting judgment.

We can provide a lambda expression to easily control the sorting, or provide a function pointer.

1
2
3
4
5
std::list<int> list{ 10, 4, 2, 5 };

std::sort(list.begin(), list.end(), [](int num1, int num2) {
    return num1 < num2;
});

This sorting method operates directly on the original container, and if you do not want the data to become dirty, you should make a copy first.

Use of containers

Using a container is a bit trickier, we need the object to support size comparison itself, or the order is controlled by some external list list.

We can use map to establish a mapping relationship between internal data and tagged data, and then read the data from the map and add it to a new list container through an external list or other means to complete the sorting.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[
    "page1", "page2", "page3"
]

std::map<String, QWidget*> map;


const StringList & list = QJsonDocument::fromJson(readAll(order.json)).toStdList();

QList<QWidget*> pages;

for (const QString& key : list) {
    pages << map[key];
}