Форум программистов, компьютерный форум, киберфорум
BinaryBlade
Войти
Регистрация
Восстановить пароль
Оценить эту запись

Краткое сравнение С++ 98 и С++ 11

Запись от BinaryBlade размещена 25.05.2023 в 10:27

Краткое сравнение “старого” С++ от “нового”



Введение



Что же я имею ввиду под определением старый и новый С++ ? На самом деле всё элементарно.
У С++ как и у любого другого языка программирования имеются версии релиза – это обусловлено его развитием: добавлением новых функций, расширением стандартной библиотеки, повышением производительности, устранением ошибок предыдущих версий и т.д.
Согласно статьям Википедии последняя актуальная и рабочая версия языка, является - C++ 20.
В данной статье я не буду говорить про последнюю версию С++. Я хочу отметить наиболее важные и основные отличия С++ 98 и С++ 11.



Почему С++ 98 и С++ 11


Вероятно, многие разработчики до сих пор используют исходный стандарт языка С++ 98. Что не удивительно, так как многие обучающие ресурсы (книги, видеоматериалы) или старые проекты, созданные до 2011 года, до сих пор поддерживаются 98-м стандартом.
Но язык не стоит на месте, и поэтому на замену с++ 98 пришёл с++ 11, который собрал в себе наиболее важные изменения за всё время существования языка. Именно поэтому я буду делать сравнение между этими версиями.
Но к сожалению, я не смогу охватить тот объём информации, который состоит из всех новых возможностей версии с++ 11 и постараюсь отделить всё самое важное и крайне необходимое из этой версии. Поехали!



Встроенные типы данных


Исходя из таблицы можно заметить, что особой разницы в типе данных не наблюдается.

типы с++98 с++11
int, short int, long int + +
long long - +
char, wchar_t + +
char16_t, char32_t - +
bool + +



Ключевое слово auto


В 11 версию языка была добавлена возможность использовать ключевое слово auto.
auto – позволяет инициализировать тип переменной или лямбда–выражения во время компиляции.
Пример
:


C++
1
2
3
4
5
6
7
8
9
10
11
12
13
class A {
public:
int varA;
 
A (int a) {varA = a}
 
};
 
auto var_int {1};  // int
auto var_bool {true}; // bool
auto var_char {"1"};  //  char
auto A = new A(1); // class A
}

Ещё одна из важных особенностей auto, это перебор коллекции из любого контейнера STL в цикле for без явного указания типа элемента коллекции:

C++
1
2
3
4
5
6
7
8
9
std::vector<int> nums = {0,1,2,3,4,5,6,7};
for (auto &e : nums)
{std::cout<< e;}  // 01234567
 
for (auto e : nums)
{std::cout<< e;}  // 01234567
 
for (auto &e : {0,1,2,3,4,5,6,7})
{std::cout<< e;} // 01234567

Удобство:
  • Нет нужды переживать за правильное использование нужного типа данных
  • Функция объявленная с auto вернёт результат, даже если тип данных возвращаемого значения изменится
  • auto позволяет сократить написание кода



Список инициализаторов initializer_list


initializer_list – является классом, на основе которого можно создать объект, для инициализации элементов массива определенного типa.

Пример:



C++
1
2
3
4
5
6
7
8
9
10
11
void Init(std::initializer<int> list){
 
for(auto &e : list) {std::cout << e;} //10011101010010
 
}
 
int main(){
 
Init({1,0,0,1,1,1,0,10,10,0,1,0});
 
}

Замечу, что если речь идёт о передаче списка инициализации объектам класса или структуры, то необходимо соблюдать передачу, соответствующую порядку определению членов структуры.

Пример:



C++
1
2
3
4
5
6
7
8
9
10
11
//c++ 11
 
MyStruct mstr_cpp_11 = {11,true,'\n'};
// теперь сравните с вариантом ниже
 
// с++ 98
MyStruct mstr_cpp_98;
 
mstr_cpp_98.number = 11;
mstr_cpp_98.active = false;
mstr_cpp_98.end = '\n';

Удобство:
  • Сокращение написания кода
  • Улучшена читаемость кода



Ключевое слово typedef и using

typedef – используется для определения альтернативного имени типа данных, либо как синоним для спецификации шаблона с указанием всех его параметров,

к примеру:


C++
1
2
typedef short int INT16;
INT16 number = 89; // short int


Но для шаблонов такой способ не подойдёт:

C++
1
2
3
4
template <typename First, typename Second, int third>
class SomeType;
 
typedef SomeType<OtherType, Second, 8> TypedefName; // Ошибка компиляции!

И наконец в с++ 11 была добавлена возможность создавать синоним имени шаблона с использованием следующего синтаксиса:

C++
1
2
3
4
template <typename First, typename Second, int third>
class SomeType;
 
using TypedefName = SomeType<OtherType, Second,8> // ОК!


Также для простых типов

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using FLOAT = float;
using PFLOAT = float*;
 
int main(){
 
using std::cout,std::endl;
 
FLOAT PI = 3.14;
 
PFLOAT p_PI;
 
 p_PI = &PI; 
 
}

Удобство:
  • Возможность создавать псевдоним для типа шаблона
  • Улучшена читаемость кода



Ключевое слово sizeof

До 11 версии языка, оператор sizeof можно было использовать только для простых типов и объектов. Например, следующая конструкция не сработает для с++ 98 ( и с++ 03):

C++
1
2
3
4
5
6
7
8
9
10
struct MyStruct{
int number;
};
 
int main(){
using std::cout, std::endl;
 
cout << sizeof(MyStruct::number) << endl;  // Ошибка компиляции!
 
}
[FONT="Microsoft Sans Serif"]Для версии 11 эта проблема решена. Тот же код сможет выполнится на с++ 11.[/FONT


Нулевой указатель nullptr


До появления с++ 11, разработчики использовали в качестве константы нулевого указателя макрос NULL (0), который указывал на область в памяти, где гарантировано отсутствуют какие-либо данные.

Проблема заключается в том, что константа 0 играла двойную роль целого числа и нулевого указателя. Это влияло на проектирование кода с использованием перегрузки функции,

например:


C++
1
2
3
4
5
6
7
void GetType(char *) {std::cout<< "char *";}
void GetType(int) {std::cout<< "int";}
 
int main(){
GetType(0);     // вывод:  int
GetType(NULL); // вывод: int     "так как NULL является тоже целым числом 0, то вызов GetType(char *) не произойдёт"
}
В С++ 11 эта проблема была решена с помощью константы nullptr:

C++
1
2
3
4
5
void GetType(char *) {std::cout<< "char *";}
void GetType(int) {std::cout<< "int";}
 
int main(){
GetType(nullptr);     // вывод:  char *
Удобство:
- Решает проблему с перегрузками функций





Ключевое слово constexpr


сonstexpr – это спецификатор типа, который гарантирует, что выражение является константным (const). Был добавлен в с++ 11 с целью инициализации константных выражений ( функций,объектов, переменных ) на этапе компиляции, для определения внешних массивов или значений перечислений.

Например:


C++
1
2
3
4
5
6
7
8
9
10
11
12
constexpr int GetNum() {
return 100;
}
 
int main(){
 
using std::cout,std::endl;
 
int arr[GetNum() + 7];
 
cout << sizeof(arr) << endl; // 428
}


В данном случае код сработает, так как выражение GetSize() + 7 заведомо является константным за счёт использования constexpr. Без указания этого спецификатора, выражение GetSize() + 7 не гарантирует, что оно является константным. То есть компилятору на данный момент неизвестно вернёт ли функция константу или нет.

Удобство:
  • Любые выражения можно сделать константными
  • Выражения с constexpr вычисляются во время компиляции (если это возможно) либо во время выполнения



Ключевое слово final


С++11 позволяет запрещать в классах-наследниках переопределение определенных методов. Достигается это за счет применения спецификатора final рядом с сигнатурой метода.


C++
1
2
3
4
5
6
7
8
9
class Base {
public:
virtual void doSomething(int x) final;
};
 
class Derived : public Base{
public:
virtual void doSomething(int x); // Ошибка компиляции! 
};
Данный спецификатор также позволяет запрещать наследование от некоторого класса.

Например:


C++
1
2
class Base final {};
class Derived : public Base {}; // Ошибка компиляции!
Спецификатор final издавна существует в Java. Наконец он появился и в C++.

Удобство:
  • Защита от переопределения виртуальной функции
  • Защита от наследования



Выражение lambda

Лямбда выражения – определяют объект анонимной функции непосредственно в месте, где она и вызывается. Чаще всего такие выражения используют для инкапсуляции данных, передаваемых в какой-либо метод или функцию, откуда она и будет в итоге вызвана в качестве callback.
Простой пример:


C++
1
2
3
4
5
6
7
8
9
using std::cout,std::endl;
 
int main(){
 
auto get_word = [](){return "Hello world!";}
 
cout << get_word() << endl;
 
}

Более сложный пример:


C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int A = 1;
int B = 2;
int C = 3;
 
auto GetWord = [=,  &B](string _word){
 
A += 2; // Ошибка компиляции! Изменение значения не сработает, захват переменной только для чтения
B += 2;  // Изменение значения сработает, захват B осуществлялся по ссылке
C += 2; // Ошибка компиляции! Изменение значения не сработает, захват переменной только для чтения
 
cout << "Вызов в анонимной функции: " <<endl;
cout << A << endl << B << endl << C << endl;
cout << _word << endl; 
 
};

Удобство:
  • Эффективно повышает читабельность программы
  • Уменьшение количества строк кода
  • Отсутствие каких либо новых строк или имен



Умные указатели (std::unique_ptr)

И последнее важное новшество в с++ 11 – это умные указатели.
Любой программист C++, вероятно, согласится с тем, что одним из самых сложных аспектов языка является ручное управление памятью. Охват всех возможных случаев и путей возврата (включая исключения) может оказаться сложной задачей даже для языковых экспертов, часто с ужасными последствиями утечек памяти или ошибок сегментации. C++11 предоставляет невероятный набор инструментов, позволяющий почти устранить эту проблему, сохраняя при этом мощь и гибкость, которые делают C++ таким замечательным языком.

Пример с простым указателем:



C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A{
public:
void ShowText()
{
 
cout << "Hello world!" << endl;}
 
};
 
int main(){
 
A* ptr_obj = new A(); // создаём объект в куче
 
ptr_obj->ShowText(); // вызываем метод объекта
 
delete ptr_obj;  // удаляем объект
}

Вроде бы всё хорошо, но есть одна проблема. Что произойдёт если ShowText() по каким – либо причинам, выбросит исключение? Тогда управление не дойдёт до delete и память, занятая ptr_obj не освободится.
Поэтому сообществом разработчиков с++ было принято решение не использовать delete для ручного освобождения памяти.
В следующем примере наглядно демонстрируется использование std::unique_ptr:


C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <memory> // обязательная библиотека для подключения умных указателей
 
class A{
public:
void ShowText()
{
 
cout << "Hello world!" << endl;}
 
};
 
int main(){
 
unique_ptr<A> ptr_obj(new A()); // создаём объект в куче
 
ptr_obj->ShowText(); // вызываем метод объекта
 
// объект ptr_obj автоматически удалится за счёт встроенного delete в unique_ptr
 
}
Даже если ShowText() выбросит исключение, память занятая ptr_obj всё равно освободится.

Удобство:
  • Автоматическое освобождение динамической памяти
  • Уменьшение количества ошибок программистов



Выводы


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


Благодарю за прочтение статьи!
Размещено в Без категории
Показов 756 Комментарии 0
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2023, CyberForum.ru