1 Разные улучшения
Введение
Стандарт С++, выпущенный в 2011 году,
расширяет возможности языка в следующих областях:
- Функциональный подход: выведение типов,lambda-функции
- Параллельное программирование
- Работа с контейнерами: циклы по диапазону, begin(), end()
- Разные улучшения: нулевые указатели, rvalue-ссылки и др.
Для подключения новых возможностей, компилятору g++ необходимо передать параметры:
- -std=c++11
Выведение типов
Выведение типов - это когда определение типа объекта перекладывается на
компилятор.
Пример:
auto first=1; // тип int
auto second=2L; // тип long
Тоже самое для классов:
class A {};
class B {};
auto *p=new A;
Основной плюс: упрощение объявлений
Пример с использованием итераторов:
for (auto itr = myvec.cbegin(); itr != myvec.cend(); ++itr)
Еще примеры:
auto otherVariable = 5;
int someInt;
decltype(someInt) otherIntegerVariable = 5;
Нулевые указатели
nullptr - новый тип нулевого значения указателя
(раньше применялся макрос NULL)
int* p1 = NULL;
int* p2 = nullptr;
Основные плюсы: лучшее преобразование типов
Циклы по диапазону
Пример использования цикла, без обозначения числа повторений и границ массива
using namespace std;
int main()
{
int arr[]={1,2,3,4,5,6,7,8,9,10};
for(int &item: arr)
cout<<item<<" ";
return 0;
}
Основные плюсы: упрощение обработки массивов и коллекций
Работает и так:
using namespace std;
void proc(int x)
{
cout<<x*x<<" ";
}
int main()
{
int arr[]={1,2,3,4,5,6,7,8,9,10};
for(int x: arr)
proc(x);
return 0;
}
Основные плюсы: упрощение обработки массивов и коллекций
Пример с обработкой коллекции:
using namespace std;
int main()
{
list<int> col;
col.push_back(rand()%100);
col.push_back(rand()%100);
col.push_back(rand()%100);
for(auto iter: col)
cout<<iter<<endl;
return 0;
}
Новый вариант перечисления
Перечисления enum class в отличие от традиционных enum
уже не преобразуются в целый тип и не являются глобальными константами.
enum class COLORS {Red, Yellow, Green};
COLORS c = COLORS::Green;
Основные плюсы: исчезли конфликты имен, улучшилась типизация
Ключевые слова final и override
Возможна ситуация, когда сигнатура виртуального метода изменена в
базовом классе или изначально неправильно задана в производном классе.
В таких случаях данный метод в классе-наследнике не будет замещать
соответствующий метод базового класса
struct Base {
virtual void some_func();
};
struct Derived : Base {
void sone_func(); // ошибка в имени функции!
};
struct B
{
virtual void some_func();
virtual void f(int);
virtual void g() const;
};
struct D1 : public B
{
void sone_func() override; // ошибка: неверное имя функции
void f(int) override; // OK: замещает такую же функцию в базовом классе
virtual void f(long) override; // ошибка: несоответствие типа параметра
virtual void f(int) const override;// ошибка: несоответствие cv-квалификации функции
virtual int f(int) override; // ошибка: несоответствие типа возврата
virtual void g() const final; // OK: замещает такую же функцию в базовом классе
virtual void g(long); // OK: новая виртуальная функция
};
struct D2 : D1
{
virtual void g() const; // ошибка: попытка замещения финальной функции
};
begin и end
Новым дополнением к стандартной библиотеке выступают функции
begin() и end(), которые работают с любыми контейнерами,
включая массивы:
using namespace std;
int main()
{
int arr[]={1,2,3};
auto i=begin(arr);
auto j=end(arr);
for(auto k=i;k<j;k++)
cout<<*k<<endl; // 1 2 3
return 0;
}
Ссылки на временные объекты
В результате вычислений появляются временные объекты, которые
передаются в функции по константной ссылке.
Вводится новый тип ссылки rvalue для более быстрой
передачи временных объектов в функции.
vector & operator= (const vector &); // Обычное присваивание (медленное)
vector & operator= (vector &&); // Перенос временного объекта (быстрый)
Например, std::vector — это простая обёртка вокруг Си-массива и переменной, хранящей его размер.
Конструктор копирования std::vector::vector(const vector &x) создаст новый массив и скопирует информацию;
конструктор переноса std::vector::vector(vector &&x) может просто обменяться указателями и переменными,
содержащими длину.
template<class T> class vector
{
vector (const vector &); // Конструктор копирования (медленный)
vector (vector &&); // Конструктор переноса из временного объекта (быстрый)
vector & operator = (const vector &);// Обычное присваивание (медленное)
vector & operator = (vector &&); // Перенос временного объекта (быстрый)
};
Обобщенные константные выражения
Результат работы функции в виде константы не мог раньше использоваться при
объявлении массива, т.к. значение было неизвестно во время компиляции.
Ключевое слово constexpr решает эту проблему.
constexpr int GiveFive() {
return 5;
}
// создание массива 12 целых; разрешено в C++11
int some_value[GiveFive() + 7];
Использование constexpr порождает очень жёсткие ограничения на действия функции:
- такая функция должна возвращать значение;
- тело функции должно быть вида return выражение;
- выражение должно состоять из констант и/или вызовов других constexpr-функций;
- функция, обозначенная constexpr, не может использоваться до определения в текущей единице компиляции.
Вызов конструкторов
Новый стандарт позволяет вызывать одни конструкторы класса из других
(так называемая делегация). Это позволяет писать конструкторы,
использующие поведение других конструкторов без внесения дублирующего кода.
Пример:
class SomeType
{
int number;
public:
SomeType(int new_number) :
number(new_number) {}
SomeType() : SomeType(42) {}
};
2 $\lambda$-функции
Механизм
-функций
lambda- функции представляют собой функции, не имеющие имени, например:
[](int x, int y) { return x + y; }
в основном, они используются там, где создавать отдельную функцию с именем
нет никакой необходимости, например в алгоритмах стандартной
библиотеки.
Немного надуманный пример (но простой):
using namespace std;
int main()
{
auto func = [] () { cout << "Hello world"; };
func(); // вызов
}
Создается переменная-функция (func), которой присваивается адрес
безымянной функции.
Более полезный пример:
using namespace std;
int main()
{
vector<int> someList;
someList.push_back(10);
someList.push_back(20);
int total = 0;
for_each(someList.begin(),
someList.end(),
[&total](int x) {
total += x;
});
cout << total;
return 0;
}
Ответ: 30
В этом примере используется перебор элементов вектора с
вызовом безымянной функции для каждого элемента. Функция получает
переменную total по ссылке, а в качестве x ей передается значение
элемента вектора.
Пример с использованием функции суммирования (с двумя аргументами):
using namespace std;
int main()
{
auto sum = [](int x, int y) { return x + y; };
cout << sum(5, 2) << endl;
cout << sum(10, 5) << endl;
}
Альтернативный вариант объявления функции с указанием
типа возвращаемого значения:
auto sum = [](int x, int y) -> int { return x + y; };
using namespace std;
int main() {
srand(time(0));
vector<int> v;
for(int i=0;i<1000;i++)
v.push_back(rand()%100);
int result=count_if(v.begin(),v.end(),
[](int val){ return (val%2)==0; });
cout<<"Even numbers count:"<<result<<endl;
return 0;
}
Можно использовать -функции в виде параметра:
....
...
int count_fun(vector<int> v, function<int (int)> fun)
{
return count_if(v.begin(),v.end(),fun);
}
...
int main() {
...
int result=count_fun(v,[](int val){ return (val%2)==0; });
cout<<"Even numbers count:"<<result<<endl;
return 0;
}
3 Параллелизм
Параллелизм - одновременное выполнение нескольких операций.

Потоки
Потоки представляют собой абстракцию параллельного выполнения
using namespace std;
void hello()
{
cout<<"Hello from thread!"<<endl;
}
int main()
{
thread t(hello);
cout<<"Hello from main!"<<endl;
t.join();
return 0;
}
Параллелизм
Существуют 2 причины использовать параллелизм:
- Разделение обязанностей
- Повышение производительности
Повышение производительности
Два способа повышения производительности:
- Распараллеливание по задачам
- Распараллеливание по данным
Естественно-параллельные алгоритмы - алгоритмы, легко поддающиеся
распараллеливанию.
Простейшая программа с 2 потоками
В следующей программе создается дополнительный поток:
using namespace std;
void hello()
{
cout<<"Hello from thread!"<<endl;
}
int main()
{
thread t(hello);
cout<<"Hello from main!"<<endl;
t.join();
return 0;
}
Пример программы с 2 потоками
Ответ:
Hello from main!
Hello from thread!
Последовательность строк при выводе может быть другой.
Пример программы с 11 потоками
Создание множества потоков:
static const int num_threads = 10;
void call_from_thread() {
cout << "Hello, World" << endl;
}
int main() {
thread t[num_threads];
for (int i = 0; i < num_threads; ++i)
t[i] = thread(call_from_thread);
cout << "Launched from the main\n";
for (int i = 0; i < num_threads; ++i)
t[i].join();
return 0;
}
Результат выполнения:
HHHHHHHeHeHeeLHeeelelellaelllllllllullllol oloonlooo,o,o,,co,,, , , h, W W WWe WWWoWoWoodWooorororr or rrlrlrllfrllldldlddrlddd d d od m the main