среда, 28 марта 2012 г.

Новые возможности С++ 2011



Прошло 13 лет как был принят первый стандарт С++. Денни Калей, бывший член комитета по стандартизации C++, объясняет как был усовершенствован язык программирование и как теперь нужно писать качественный код на С++.
Создатель языка C++, Бьерн Страуструп, недавно сказал, что C++11 это «практически новый язык – теперь все его составные части лучше дополняют друг друга». Теперь он поддерживает лямбда-выражения, автоматический вывод типа объекта, унифицированный синтаксис при инициализации, делегирующие конструкторы, объявления удаленных функций и функций по умолчанию, nullptr, и наиболее важное новшество - ссылочные rvalue – механизм, который позволяет использовать семантику переноса при создании ссылочных объектов. И это только лишь небольшой пример того, что нового появилось в С++.
Помимо этого в стандартной библиотеке C++11 появились новые алгоритмы, новые контейнеры, атомные операции, цепочки данных, регулярные выражения, новые умные указатели, выражение async(), и конечно же многопоточная библиотека.
Полный список  нововведений  в С++11 можно посмотреть здесь.
После принятия стандарта С++ в 1998 году, некоторые члены комитета по стандартизации пророчили, что в последующих редакциях стандарта обязательно появится встроенный сборщик мусора, который, правда, не сможет использоваться в многопоточных приложениях изза технических сложностей реализации новой поточной модели. Тринадцать лет спустя, вышел новый стандарт C++11. И угадайте что? В нем нету убощика мусора, но присутствует новая многопоточная библиотека.
В данной публикации я поговорю о самых значимых изменениях в языке, и о том, почему они такие значимые. Как вы видите, многопоточная библиотека далеко не единственное новшество языка. Новый стандарт основан на десятилетиях опыта и делает C++ еще более актуальным. Как подчеркнул Роджер Каденхерт: “Это поистине удивительно, как нечто старое, вроде диско, Пит Рокса и олимписких плавцов с волосатой грудью ”
Итак, давайте посмотрим на значимые особенности нового языка C++11.

Лямбда выражения

Лямбда выражения позволяют локально определить функцию, в месте ее вызова, и таким образом позволяют решить множесто проблем с безопасностью используемых в функции объектов и переменных. Лямбда выражения имеют следующую форму:
[захват](параметры)->возвращаемый-тип {тело}
Конструкция [] внутри списка параметров вызова функции определяет начало лямбда выражения. Давайте обратимся к примеру использования лямбда-выражения.
Предположим, вам необходимо посчитать сколько заглавных букв содержится в строке. Используя инструкцию for_each() для прохода по массиву смволов, следующее лямбда-выражение определяет является ли текущая буква заглавной. При нахождении заглавной буквы, лямбда-выражение каждый раз увеличивает счетчик Uppercase, который объявлен за пределами лямбда-выражения.

int main()
{
   char s[]="Hello World!";
   int Uppercase = 0; //modified by the lambda
   for_each(s, s+sizeof(s), [&Uppercase] (char c) {
    if (isupper(c))
     Uppercase++;
    });
 cout<< Uppercase<<" uppercase letters in: "<< s<
}

Выглядит это так,  как будто бы вы определили отдельную функцию внутри вызова другой функции. Амперсанд в выражении [&Uppercase] говорит о том, что в тело выражения переменная передается по ссылке и таким образом может выражение может изменять значение Uppercase. Без амперсанда переменная была бы передана по значению. Лямбда выражения C++11 также могут содержать кнструкции для членов функций.

Автоматическое выведение типа и decltype

В C++03 всякий раз необходимо указывать тип объекта при объявлении. В большинстве случаев, при объявлении осуществляется и инициализация. В C++11 этот факт можно использовать для того, что бы не указывать тип объекта при его объявлении:

auto x=0; //x has type int because 0 is int
auto c='a'; //char
auto d=0.5; //double
auto national_debt=14400000000000LL;//long long

Автоматическое выведение типов особенно полезно в случаях, когда имя типа громоздко или когда оно генерируется автоматически (в шаблонах например). Рассмотрим ситуацию:

void func(const vector &vi)
{
vector::const_iterator ci=vi.begin();
}

Вместо такого кода, итератор можно объявить вот так:

auto ci=vi.begin();

Ключевое слово auto не новое в C++; оно использовалось в языке и ранее, правда для совсем других целей. Теперь оно не указывает о автоматическом типе хранения объекта. Теперь это является индикацией того, что тип объекта выводится автоматически в соответствии с его первичной инициализацией. Для того что бы предотвратить двусмысленное трактование этого ключевого слова, его старый смысл был удален из языка.
C++11 предлагает похожий мехнизм для определения типа объекта или выражения. Новый оператор decltype из входного выражения выводит и возвращает его тип:

const vector vi;
typedef decltype (vi.begin()) CIT;
CIT another_const_iterator;

Уницифированный синтаксис инициализации

В С++ есть как минимум четыре вида инициализации, часть из которых перекрывают друг друга.
Инициализация с помощью скобок выглядит следующим образом:

std::string s("hello");
int m=int(); //default initialization

Также в большинстве случаев можно использовать знак присваивания:

std::string s="hello";
int x=5;

Для агрегатных типов можно использовать фигурные скобки:

int arr[4]={0,1,2,3};
struct tm today={0};

Наконец, в конструкторах можно использовать инициализацию членов:

struct S {
 int x;
 S(): x(0) {} };

Это многообразие – источник путаницы не только для новичков. Хуже всего то, что в С++03 невозможно проинициализировать, скажем, целочисленный массив с помощью new[]. С++11 решает эту проблему с помощью цнификации способ инициализации:

class C
{
int a;
int b;
public:
 C(int i, int j);
};

C c {0,0}; //C++11 only. Equivalent to: C c(0,0);

int* a = new int[3] { 1, 2, 0 }; /C++11 only

class X {
  int a[4];
public:
  X() : a{1,2,3,4} {} //C++11, member array initializer
};

Говоря о контейнерах, вы можете попрощаться с длинным списком вызовов push_back(). В С++11 контейнеры можно инициализировать более интуитивно:

// C++11 container initializer
vector vs={ "first", "second", "third"};
map singers =
  { {"Lady Gaga", "+1 (212) 555-7890"},
    {"Beyonce Knowles", "+1 (212) 555-0987"}};

Подобным образом в C++11 появилась поддержка инициализации членов класса внутри класса:

class C
{
 int a=7; //C++11 only
public:
 C();
};

Удаленные функции и функции по умолчанию

Функция следующего вида называется функцией по умолчанию:

struct A
{
 A()=default; //C++11
 virtual ~A()=default; //C++11
};

Часть выражения =default; информирует компилятор он необходимости генерации реализации функции по умолчанию. Подобная функциональность обладает следущими достоинствами: сгенерированная реализация является более эффективной чем написанный вручную код, программисту не нужно тратить время на написание вручную данного кода.
Противоположным смыслом обладают удаленные функции:

int func()=delete;

Удаленные функции полезны для предотвращения копирования объекта. Ввиду того что C++ автоматически генерирует конструктор копирования и оператор присваивания для классов. Для того что бы запретить копирование, необходимо объявить эти методы и пометить их как удаленные с помощью записи вида:

struct NoCopy
{
    NoCopy & operator =( const NoCopy & ) = delete;
    NoCopy ( const NoCopy & ) = delete;с
};
NoCopy a;
NoCopy b(a); //compilation error, copy ctor is deleted

nullptr

Наконец, С++ имеет ключевое слово, которое определяет константу нулевого указателя. nullptr заменил на этом поприще макрос NULL, который часто был причиной ошибок в коде на протяжении многих лет. nullptr – строго типизирован:

void f(int); //#1
void f(char *);//#2
//C++03
f(0); //which f is called?
//C++11
f(nullptr) //unambiguous, calls #2

nullptr применим к указателям любых типов, включая указатели на функции и указатели на члены:

const char *pc=str.c_str(); //data pointers
if (pc!=nullptr)
  cout<
int (A::*pmf)()=nullptr; //pointer to member function
void (*pmf)()=nullptr; //pointer to function

Делегирующие конструкторы

В С++11 конструктор может вызывать другой конструктор того же класса:

class M //C++11 delegating constructors
{
 int x, y;
 char *p;
public:
 M(int v) : x(v), y(0),  p(new char [MAX])  {} //#1 target
 M(): M(0) {cout<<"delegating ctor"<
};

Конструктор #2 – делегирующий конструктор, при вызове он вызывает целевой конструктор #1.

Ссылочные Rvalue

Ссылочные типы в С++03 могли привязываться только к lvalue. С++11 предоставляет новую категорию ссылочных типов, которые называются ссылочные rvalue. Ссылочные rvalue могут привязываться к объектам rvalue, т.е. к временным объектам и литералам.
Главной причиной добавления новой функциональности послужила реализация семантки переноса. В отличие от традиционного копирования, процесс переноса предполагает, что конечный объект будет использовать ресурсы исходного объекта, очистив при этом состояние источника. Как правило создание копии объекта избыточно и не нужно, в таких случаях целесообразно использовать операцию переноса. Для того что бы определить насколько эффективнее оакжется перенос по сравнению с копированием рассмотрим пример осуществляющий обмена содержимым двух строк. Простейшая реализация может выглядеть вот так:

void naiveswap(string &a, string & b)
{
 string temp = a;
 a=b;
 b=temp;
}

Подобная реализация неэффективна. Подобное копирование строк влечет за собой дополнительное выделение памяти  и копирование символов из исходной строки в конечную. С другой стороны, перемещение строк просто меняет данные двух членов, без выделения памяти, копирования массивов символов и удаления памяти:

void moveswapstr(string& empty, string & filled)
{
//pseudo code, but you get the idea
 size_t sz=empty.size();
 const char *p= empty.data();
//move filled's resources to empty
 empty.setsize(filled.size());
 empty.setdata(filled.data());
//filled becomes empty
 filled.setsize(sz);
 filled.setdata(p);
}

Если необходимо реализовать класс, который поддерживает перенос данных, можно объявить конструктор и оператор присваивания, как это представлено ниже:

class Movable
{
Movable (Movable&&); //move constructor
Movable&& operator=(Movable&&); //move assignment operator
};

Стандартная библиотека С++11 активно использует семантику переноса. Многие алгоритмы и контейнеры подверглись данной оптимизации.


Стандартная библиотека С++11

С++ подвергся значительным изменениям в 2003 в связи с выходом Library Technical Report 1 (TR1). В документе были представлены новые контейнеры (unordered_set, unordered_map, unordered_multiset, и unordered_multimap) и несколько библиотек для регулярных выражений, кортежей, функций, оберток над объектами и прочее. С принятием С++11 TR1 оффициально стал частью стандарта C++, вместе с новыми библиотеками, которые были добавлены после появления TR1. Вот некоторые из библиотек, которые сейчас являются стандартными:

Поточная библиотека

Бесспорно, самое важное новшество в С++11 с точки зрения программиста – это многопоточность. В С++11 появился класс потока, который представляет исполняемый поток, объекты синхронизации для синхронизации потоков в многопоточной среде, шаблонная функция async(), предназначенная для запуска паралельных задач, и тип хранения thread_local для объявления данных доступных только внутри потока. В качестве вводного экскурса в многопоточную библиотеку С++11 можно познакомится с публикацией Энтони Уильямса «Simpler Multithreading in C++0x».

Новые классы умных указателей

В С++98 был объявлен только один умный указатель auto_ptr, который сейчас считается устаревшим. С++11 содержит новые классы умных указателей: shared_ptr и unique_ptr.Оба указателя совместимы с другими компонентами стандартной библиотеки, это значит что их можно свободно использовать вместе с контейнерами и манипулировать ими с помощью стандартных алгоритмов.

Новые алгоритмы

Стандартная библиотека С++11 содержит новые алгоритмы, которые эмулируют операции из теории множеств all_of(), any_of() и none_of(). Приведенный ниже пример использует предикат ispositive() для диапазона [first, first+n) и использует all_of(), any_of() и none_of() для того что бы определить свойства диапазона.

#include
//C++11 code
//are all of the elements positive?
all_of(first, first+n, ispositive()); //false
//is there at least one positive element?
any_of(first, first+n, ispositive());//true
// are none of the elements positive?
none_of(first, first+n, ispositive()); //false

Также стала доступной новая категория алгоритмов copy_n. Используя copy_n(), копирование массива из пяти элементов в другой масив может выглядеть следующим образом:

#include
int source[5]={0,12,34,50,80};
int target[5];
//copy 5 elements from source to target
copy_n(source,5,target);

Алгоритм iota() создает диапазон последовательно увеличивающихся значений, как будто первоначальное значение было присвоено адресу *first а каждому последующему соответственно +1. В данном примере iota() используется для присваивания последовательности {10, 11, 12, 13, 14} массиву arr и последовательности {‘a’, ‘b’, ‘c’} массиву с соответственно.

#include
int a[5]={0};
char c[3]={0};
iota(a, a+5, 10); //changes a to {10,11,12,13,14}
iota(c, c+3, 'a'); //{'a','b','c'}

В С++11 по прежнему отсутствуют некоторые полезные библиотеки, например для работы с XML, сокетами, пользовательским интерфейсом, рефлексией – и разумеется достойный автоматический сборщик мусора. Однако, С++11 предлагает множество новых возможностей, которые позовлят сделать язык более безопасным, эффективным (да, еще более эффективным, чем раньше. Для справки можно обратится к результатам тестирования Google) и более простым для использования.
Если изменения в С++11 кажутся вам чрезмерными, не паникуйте. Потратьте время на изучение нововведений. В конце это процесса вы вероятно согласитесь со Страуструпом: C++11 выглядит как новый язык – лучше прежнего.

Комментариев нет: