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 причины использовать параллелизм:


  1. Разделение обязанностей
  2. Повышение производительности

Повышение производительности

Два способа повышения производительности:


  1. Распараллеливание по задачам
  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