Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Обзор новых вероятностей С 14: Часть 1

Anna | 25.06.2014 | нет комментариев

В апреле в Бристоле прошла встреча комитета С , на которой были рассмотрены первые предложения по внесению изменений в новейший эталон С 14. Все рассматриваемые в этой статье метаморфозы были одобрены на этой встрече и теснее занимают свое почетное место в последней версии черновика нового эталона (N3690 от 15 мая 2013).

Короткий перечень:

  • Механическое определение типа возвращаемого значения для обыкновенных функций
  • Обобщенная инициализация захваченных переменных лямбд с помощью захвата-по-перемещению
  • Обобщенные (полиморфные) лямбда-выражения
  • Упрощенные ограничения на создание constexpr функций
  • Образцы переменных
  • exchange
  • make_unique
  • Обособленные строки
  • Пользовательские литералы для типов стандартной библиотеки
  • optional
  • shared_mutex и shared_lock
  • dynarray

Метаморфозы в самом языке

Механическое определение типа возвращаемого значения для обыкновенных функций

Начиная с С 11 в языке возникла вероятность определять лямбда-выражения, для которых, в случае, если у Вас каждого один return оператор, дозволено не указывать тип возвращаемого значения — компилятор может вывести его самосильно. Многие были поражены тем, что такая вероятность отсутствует для обыкновенных функций. Соответствующее предложение было внесено еще до выхода С 11, впрочем его отложили из-за ограничений по времени, и вот сейчас, через несколько лет, его добавили в эталон. Так же, данная поправка говорит о том, что если функция либо лямбда-выражение имеют несколько операторов return, возвращающих один и тот же тип, то в данном случае, компилятор тоже должен выводить значение типа механически, включая случай с рекурсивным вызовом.

auto iterate(int len)    // возвращаемое значение - int
{
  for (int i = 0; i < len;   i)
    if (search (i))
      return i;
  return -1;
}

auto h() { return h(); } // оплошность, тип возвращаемого значения не знаменит

auto sum(int i) {
  if (i == 1)
    return i;           // возвращаемое значение - int
  else
    return sum(i-1) i;     // сейчас дозволено вызывать рекурсивно
}

template <class T> auto f(T t) { return t; } // тип будет выведен во время инстанцирования

[]()->auto& { return f(); }                  // возврат ссылки

Невзирая на то, что данная вероятность имеет огромный потенциал, она имеет некоторые ограничения:
Если Вы поместите объявление функции в заголовочном файле, а определение в соответствующий файл с начальным кодом, то при подключении заголовочного файла в другие файлы, компилятор не сумеет вывести тип:

// foo.h
class Foo {
public:
  auto getA() const;
};

// foo.cpp
#include "foo.h"

auto Foo::getA() const
{
  return something;
}

// main.cpp
#include "foo.h"

int main() {
  Foo bar;
  auto a = bar.getA(); // оплошность, немыслимо вывести тип
}

А значит применять это получится только с локальными функциями либо с функциями, определенными в заголовочных файлах. К последним, как правило, относятся шаблонные функции — основное, на мой взор, использование для данной новинки.
К ограничениям, определенным в эталоне, относятся: закроет на применение с виртуальными функциями и закроет на возврат объекта типа std::initializer_list.

Обобщенная инициализация захваченных переменных лямбд с помощью захвата-по-перемещению

В С 11 лямбды не поддерживают завладение-по-перемещению (capture-by-move). Скажем, дальнейший код не будет скомпилирован:

#include <memory>
#include <iostream>
#include <utility>

template <class T> void run(T&& runnable)
{
  runnable();
};

int main()
{
  std::unique_ptr<int> result(new int{42});
  run([result](){std::cout << *result << std::endl;});
}

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

[ x { move(x) }, y = transform(y, z), foo, bar, baz ] { ... } 

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

int x = 4;
auto y = [&r = x, x = x 1]()->int {
            r  = 2;
            return x 2;
         }();  // Обновляет ::x до 6, и инициализирует y 7-кой.

Не запрещено и очевидное указание типа инициализируемых захваченных переменных:

[int x = get_x()] { ... } 
[Container y{get_container()}] { ... }
[int z = 5] { ... }

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

[&&x] { ... } 

Задача в том, что мы не захватываем по rvalue ссылке, мы пытаемся переместить. Если бы мы пошли таким методом, то перемещение могло бы случиться значительно позднее, когда настоящий объект теснее может не существовать. Мы обязаны перемещать объект во время захвата переменных, а не во время вызова лямбды.

Обобщенные (полиморфные) лямбда-выражения

В С 11 лямбда выражения создают объекты класса, имеющего нешаблонный оператор вызова функции. Данная поправка предлагает:

  • Дозволить применять auto тип-спецификатор, обозначающий обобщенный (шаблонный) лямбда параметр
  • Дозволить реформирование из лямбда функции, не захватыющей значения, к соответствующему указателю-на-функцию

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

void f1(int (*)(int))   { }
void f2(char (*)(int))  { }

void g(int (*)(int))    { }  // #1
void g(char (*)(char))  { }  // #2

void h(int (*)(int))    { }   // #3
void h(char (*)(int))   { }   // #4

auto glambda = [](auto a) { return a; };
f1(glambda);  // OK
f2(glambda);  // оплошность: ID не конвертируем
g(glambda);   // оплошность: двусмысленно
h(glambda);   // OK: вызывает #3, так как может быть сконвертировано из ID
int& (*fpi)(int*) = [](auto* a) -> auto& { return *a; }; // OK

Не забываем про вероятность применения совместно с универсальными ссылками и образцами переменного числа доводов (variadic templates):

auto vglambda = [](auto printer) {
   return [=](auto&& ... ts) {   // OK: ts - это упакованные параметры функции
       printer(std::forward<decltype(ts)>(ts)...);
   };
};
auto p = vglambda( [](auto v1, auto v2, auto v3)    
                       { std::cout << v1 << v2 << v3; } );
p(1, 'a', 3.14);  // OK: выводит 1a3.14

Упрощенные ограничения на создание constexpr функций

Внесенные метаморфозы разрешают в constexpr функциях применять:

  • Объявление переменных (за исключением staticthread_local и неинициализированных переменных)
  • if и switch (но не goto)
  • for (включая range-based for), while и do-while
  • Метаморфоза состояния объектов, являющихся итогом constexpr вычислений
constexpr int abs(int x) {
  if (x < 0)
    x = -x;
  return x;                     // OK
}

constexpr int first(int n) {
  static int value = n;         // оплошность: статическая переменная
  return value;
}

constexpr int uninit() {
  int a;                        // оплошность: неинициализированная переменная
  return a;
}

constexpr int prev(int x)
  { return --x; }               // OK

constexpr int g(int x, int n) { // OK
  int r = 1;
  while (--n > 0) r *= x;
  return r;
}

В дополнение, было удалено правило, по которому constexpr нестатические функции-члены неявно получалиconst спецификатор (подробнее об этом тут).

Образцы переменных

Данная вероятность разрешает создавать и применять constexpr образцы переменных, для больше комфортного сочетания с шаблонными алгорифмами (допустимо применение не только со встроенными типами, но и с типами, определенными пользователем):

template<typename T>
constexpr T pi = T(3.1415926535897932385);

template<typename T>
T circular_area(T r) {
  return pi<T> * r * r;
}

struct matrix_constants {
  template<typename T>
  using pauli = hermitian_matrix<T, 2>;

  template<typename T>
  constexpr pauli<T> sigma1 = { { 0, 1 }, { 1, 0 } };

  template<typename T>
  constexpr pauli<T> sigma2 = { { 0, -1i }, { 1i, 0 } };

  template<typename T>
  constexpr pauli<T> sigma3 = { { 1, 0 }, { -1, 0 } };
};

Метаморфозы в стандартной библиотеке

exchange

Атомарные (atomic) объекты предоставляют atomic_exchange функцию, которая разрешает назначить объекту новое значение и воротить его ветхое значение. Сходственная функция может быть пригодна и для обыкновенных объектов.

// возможная реализация
template<typename T, typename U=T>
T exchange(T& obj, U&& new_val) {
  T old_val = std::move(obj);
  obj = std::forward<U>(new_val);
  return old_val;
}

Для притивных типов эта функция делает тоже самое, что и обыкновенная реализация. Для трудных типов эта функция разрешает:

  • Избежать копирования ветхого объекта, если для этого типа определен конструктор перемещения
  • Принимать всякое тип в качестве нового значения, получая превосходство применения всякого конвертирующего оператора присваивания
  • Избежать копирования нового объекта, если он непостоянный либо был перемещен сюда

Скажем, реализация std::unique_ptr::reset:

template<typename T, typename D>
void unique_ptr<T, D>::reset(pointer p = pointer()) {
  pointer old = ptr_;
  ptr_ = p;
  if (old)
    deleter_(old);
}

может быть усовершенствована до:

template<typename T, typename D>
void unique_ptr<T, D>::reset(pointer p = pointer()) {
  if (pointer old = std::exchange(ptr_, p))
    deleter_(old);
}

make_unique

В С 11, совместно с мудрыми указателями, возникла и такая функция, как make_shared. Она разрешает оптимизировать выделение памяти для shared_ptr, а так же повысить безопасность касательно исключений. И правда аналогичная оптимизация для unique_ptr немыслима, то повышенная безопасность касательно исключений никогда не повредит. Скажем тут, в обоих случаях, мы можем получить утрату памяти, если при создании одного объекта кинулось исключение, а 2-й объект теснее сделан, но еще не размещен вunique_ptr:

void foo(std::unique_ptr<A> a, std::unique_ptr<B> b);

int main() {
  foo(new A, new B);
  foo(std::unique_ptr<A>{new A}, foo(std::unique_ptr<B>{new B});
}

Дабы решить эту обстановку, было решено добавить функцию make_unique. Таким образом С 14 фактически всецело (за исключение дюже редких случаев) предлагает программистам отказаться от операторов new иdelete.
Пример применения:

#include <iostream>
#include <string>
#include <memory>
using namespace std;

void foo(std::unique_ptr<string> a, std::unique_ptr<string> b) {}

int main() {
    cout << *make_unique<int>() << endl;
    cout << *make_unique<int>(1729) << endl;
    cout << """ << *make_unique<string>() << """ << endl;
    cout << """ << *make_unique<string>("meow") << """ << endl;
    cout << """ << *make_unique<string>(6, 'z') << """ << endl;

    auto up = make_unique<int[]>(5);

    for (int i = 0; i < 5;   i) {
        cout << up[i] << " ";
    }

    cout << endl;

    foo(make_unique<string>(), make_unique<string>()); // утрата памяти немыслима

    auto up1 = make_unique<string[]>("error"); // оплошность
    auto up2 = make_unique<int[]>(10, 20, 30, 40); // оплошность
    auto up3 = make_unique<int[5]>(); // оплошность
    auto up4 = make_unique<int[5]>(11, 22, 33, 44, 55); // оплошность
}
/* Output:
0
1729
""
"meow"
"zzzzzz"
0 0 0 0 0
*/

Обособленные строки

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

std::stringstream ss;
std::string original = "foolish me";
std::string round_trip;

ss << original;
ss >> round_trip;

std::cout << original;   // итог: foolish me
std::cout << round_trip; // итог: foolish

assert(original == round_trip); // assert сработает

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

std::stringstream ss;
std::string original = "foolish me";
std::string round_trip;

ss << quoted(original);
ss >> quoted(round_trip);

std::cout << original;     // итог: foolish me
std::cout << round_trip;   // итог: foolish me

assert(original == round_trip); // assert не сработает

Если строка теснее содержит кавычки в себе, они будут так же обособлены:

std::cout << "She said "Hi!"";  // итог: She said "Hi!"
std::cout << quoted("She said "Hi!"");  // итог: "She said "Hi!""

Данная поправка основана на boost аналоге.

Пользовательские литералы для типов стандартной библиотеки

C 11 вводит представление пользовательских литералов (ПЛ), но ни одного ПЛ не было определенно для стандартной библиотеки, правда согласно эталону, имена ПЛ начинающиеся не с символа нижнего подчеркивания, зарезервированы за STL.
Соответствующая поправка добавляет в эталон следующие пользовательские литералы:

  • Оператор s для basic_string
  • Операторы hminsmsusns для типов, входящих в chrono::duration

Скажем:

auto mystring = "hello world"s;   // тип std::string

auto mytime = 42ns;               // тип chrono::nanoseconds

Пользовательские литералы s не конфликтуют, от того что они принимают различные типы параметров.

optional

Данная поправка вводит в стандартную библиотеку новейший тип, обозначающий добровольный объект. Допустимый методы применения:

  • Вероятность указать, какие параметры функций необязательны
  • Вероятность применять null-состояние (без применения обыкновенных (raw) указателей)
  • Вероятность ручного контроля времени жизни RAII объектов
  • Вероятность пропустить дорогие (типовые) конструкторы объектов

Приблизительный метод применения:

optional<int> str2int(string);    // приводит строку к целому числу, если допустимо

int get_int_form_user()
{
  string s;

  for (;;) {
    cin >> s;
    optional<int> o = str2int(s); // 'o' может содержать целое число, а может и нет
    if (o) {                      // содержит ли'o' целое число?
      return *o;                  // используем число
    }
  }
}

Данная поправка основана на boost аналоге.
Перевод статьи автора этой поправки об этом классе дозволено обнаружить тут.

shared_mutex и shared_lock

При разработке многопоточных программ изредка возникает надобность дать к некоторому объекту множественный доступ на чтение либо неповторимый доступ на запись. Данная поправка добавляет в стандартную библиотеку shared_mutex, предуготовленный для этой цели. Функция lock дает неповторимый доступ, и может быть использона с добавленными ранее lock_guard и unique_lock. Дабы получить объединенный доступ, нужно применять lock_shared функцию, и отменнее это делать через добавленый RAII класс shared_lock:

using namespace std;
shared_mutex rwmutex;
{
  shared_lock<shared_mutex> read_lock(rwmutex);
  // чтение
}
{
  unique_lock<shared_mutex> write_lock(rwmutex); // либо lock_guard
  // запись
}

Стоит подметить, что стоимость всякого лока, даже на чтение, подороже, чем при применении обыкновенного мьютекса.
Данная поправка основана на boost аналоге.

dynarray

В нынешнем эталоне С есть динамические массивы, размер которых может быть определен только во время выполнения. Как и у обыкновенных массивов, данные у них создаются на стэке. С 14 заявляет такие массивы, но при этом определяет еще и свой личный вид динамический массив — std::dynarray, тот, что так же узнает свой размер во время выполнения и не может изменять его в последующем, впрочем может либо расположить объекты на стэке, либо поместить их в куче, при этом имея интерфейс во многом схожий сstd::array и std::vector. Согласно поправке, данный класс может быть очевидно оптимизирован компилятором, для случаев применения стэка.

Завершение

В основном, С 14 позиционируется, как minor bug-fix release, исправляя недочеты С 11, допущенные из-за ограничений по времени либо по каким-либо иным причинам. Нынешний черновик эталона С дозволено обнаружить тут.

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB