В данной статье я хотел бы рассказать, как использование средств современных стандартов С++ позволяет повысить производительность программ без каких-либо особых усилий от программиста.
Эта статья затрагивает лишь средства языка, а не конкретные техники оптимизации (т.е. такие вещи как локальность памяти, платформозависимые оптимизации, lockfree и прочее остаются за бортом).
Ключевое слово final
Виртуальный метод класса, объявленный с использованием ключевого слова final, не может быть переопределен наследниками.
class A {
public:
virtual void f() {}
};
class B : public A {
public:
virtual void f() override final {}
};
class C : public B {
public:
virtual void f() override {} // ошибка
};
В некоторых случаях это позволяет компилятору избежать обычных расходов на вызов виртуальной функции (девиртуализация).
Пример:
class A {
public:
virtual void f() = 0;
};
class B1 : public A {
public:
void f() final override;
};
class B2 : public A {
public:
void f() override;
};
void with_final(B1* b) {
b->f();
}
void no_final(B2* b) {
b->f();
}
Ассемблерный код (здесь и далее: ggc 6.2, -O3 -std=c++14):
with_final(B1*):
jmp B1::f()
no_final(B2*):
mov rax, QWORD PTR [rdi]
jmp [QWORD PTR [rax]]
Не передавайте умные указатели по ссылке
Умные указатели должны использоваться для определения срока жизни объекта. Не нужно передавать умные указатели по ссылке в метод, если он не производит никаких операций с самим объектом умного указателя, а лишь с объектом, хранящимся в нём.
Пример:
#include <memory>
void f1(std::unique_ptr<int>& i) {
*i += 1;
}
void f2(int& i) {
i += 1;
}
f1(std::unique_ptr<int, std::default_delete<int> >&):
mov rax, QWORD PTR [rdi]
add DWORD PTR [rax], 1
ret
f2(int&):
add DWORD PTR [rdi], 1
ret
Используйте Rule of zero
Rule of zero гласит, что для класса, в котором не нужно явно определять деструктор, также не нужно явно определять конструкторы/операторы копирования/перемещения.
Явное определение конструктора копирования запрещает компилятору генерировать конструктор перемещения, что в некоторых случаях может значительно снизить производительность.
Пример:
#include <string>
class A {
std::string s;
public:
A() = default;
A(const A& a) = default; // конструктор перемещения не будет сгенерирован
};
class B {
std::string s;
public:
B() = default;// конструктор копирования и перемещения сгенерирован автоматически
};
auto f()
{
return std::make_pair(A(), B());// для А будет вызван конструктор копирования, для B - перемещения
}
Предпочитайте emplace копированию
Начиная с С++11 стандартные контейнеры позволяют конструировать элемент напрямую внутри контейнера, избегая лишнего копирования.
Использования emplace быстрее не только простого копирования, но даже перемещения.
Не используйте shared_ptr, если можно обойтись unique_ptr
Использования shared_ptr несёт за собой определённые расходы. При создании, копировании, удалении shared_ptr обновляет внешний счётчик ссылок на хранимый объект. Также shared_ptr обязан быть потокобезопасным, что тоже может нести за собой соответствующие расходы. В то время как выделение и удаление памяти с использованием unique_ptr вообще никак не отличается от использования ручного управления памятью с использованием new/delete.
Спасибо за внимание!
Автор: Satus