Прошло 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
{
vector::const_iterator
ci=vi.begin();
}
Вместо такого кода, итератор можно объявить вот так:
auto ci=vi.begin();
Ключевое слово auto не новое в C++; оно использовалось в языке и ранее, правда для совсем других целей. Теперь оно не указывает о автоматическом типе хранения объекта. Теперь это является индикацией того, что тип объекта выводится автоматически в соответствии с его первичной инициализацией. Для того что бы предотвратить двусмысленное трактование этого ключевого слова, его старый смысл был удален из языка.
C++11 предлагает
похожий мехнизм для определения типа объекта или выражения. Новый оператор decltype из входного
выражения выводит и возвращает его тип:
const vector
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 выглядит
как новый язык – лучше прежнего.
http://www.softwarequalityconnection.com/2011/06/the-biggest-changes-in-c11-and-why-you-should-care/
Комментариев нет:
Отправить комментарий