Пользователь
0,0
рейтинг
11 июня 2013 в 00:27

Разработка → Десять возможностей C++11, которые должен использовать каждый C++ разработчик

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

Сегодня в программе:
  • auto
  • nullptr
  • range-based циклы
  • override и final
  • строго-типизированный enum
  • интеллектуальные указатели
  • лямбды
  • non-member begin() и end()
  • static_assert и классы свойств
  • семантика перемещения

#1 — auto


До С++11, ключевое слово auto использовалось как спецификатор хранения переменной (как, например, register, static, extern). В С++11 auto позволяет не указывать тип переменной явно, говоря компилятору, чтобы он сам определил фактический тип переменной, на основе типа инициализируемого значения. Это может использоваться при объявлении переменных в различных областях видимости, как, например, пространство имен, блоки, инициализация в цикле и т.п.
auto i = 42;        // i - int
auto l = 42LL;      // l - long long
auto p = new foo(); // p - foo*

Использование auto позволяет сократить код (если, конечно, тип не int, который на одну букву меньше). Подумайте об итераторах STL, которые вы должны были всегда писать для прохода контейнеров. Таким образом, это делает устаревшим определение typedef только ради простоты.
std::map<std::string, std::vector<int>> map;
for(auto it = begin(map); it != end(map); ++it) 
{
   // do smth
}

// Или, сравним С++03 и С++11
// C++03
for (std::vector<std::map<int, std::string>>::const_iterator it = container.begin(); it != container.end(); ++it)
{
   // do smth
}
// C++11
for (auto it = container.begin(); it != container.end(); ++it)
{
   // do smth
}

Стоить отметить, что возвращаемое значение не может быть auto. Однако, вы можете использовать auto вместо типа возвращаемого значения функции. В таком случае, auto не говорит компилятору, что он должен определить тип, он только дает ему команду искать возвращаемый тип в конце функции. В примере ниже, возвращаемый тип функции compose — это возвращаемый тип оператора +, который суммирует значения типа T и E.
template <typename T, typename E>
auto compose(T a, E b) -> decltype(a+b) // decltype - позволяет определить тип на основе входного параметра
{
   return a+b;
}
auto c = compose(2, 3.14); // c - double


#2 — nullptr


Раньше, для обнуления указателей использовался макрос NULL, являющийся нулем — целым типом, что, естественно, вызывало проблемы (например, при перегрузке функций). Ключевое слово nullptr имеет свой собственный тип std::nullptr_t, что избавляет нас от бывших проблем. Существуют неявные преобразования nullptr к нулевому указателю любого типа и к bool (как false), но преобразования к целочисленных типам нет.
void foo(int* p) {}

void bar(std::shared_ptr<int> p) {}

int* p1 = NULL;
int* p2 = nullptr;   

if(p1 == p2)
{}

foo(nullptr);
bar(nullptr);

bool f = nullptr;
int i = nullptr; // ошибка: для преобразования в int надо использовать reinterpret_cast

#3 — range-based циклы


В С++11 была добавлена поддержка парадигмы foreach для итерации по набору. В новой форме возможно выполнять итерации в случае, если для объекта итерации перегружены методы begin() и end().

Это полезно, когда вы просто хотите получить элементы массива/контейнера или сделать с ними что-то, не заботясь об индексах, итераторах или кол-ве элементов.
std::map<std::string, std::vector<int>> map;
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
map["one"] = v;

for(const auto &kvp: map) 
{
   std::cout << kvp.first << std::endl;
   for(auto v: kvp.second)
      std::cout << v << std::endl;
}

int arr[] = {1,2,3,4,5};

for(int &e: arr) 
   e *= e;

#4 — override и final


Мне всегда не нравились виртуальные функции в С++. Ключевое слово virtual опционально и поэтому немного затрудняло чтение кода, заставляя вечно возвращаться в вершину иерархии наследования, чтобы посмотреть объявлен ли виртуальным тот или иной метод. Я всегда использовал этой ключевое слово так же и в производных классах (и поощрял людей, кто так делал), чтобы код был понятнее. Тем не менее, есть ошибки, которые могут все таки возникнуть. Возьмем следующий пример:
class B 
{
public:
   virtual void f(short) {std::cout << "B::f" << std::endl;}
};

class D : public B
{
public:
   virtual void f(int) {std::cout << "D::f" << std::endl;}
};

D::f переопределяет B::f. Однако они имеют разную сигнатуру, один метод принимает short, другой — int, поэтому B::f — это просто другой метод с тем же именем, перегруженный, а не переопределенный. Таким образом, работая через указатель на базовый класс, Вы можете вызвать f() и ожидать вывода «переопределенного» вами метода: «D::f», однако вывод будет «B::f».

Вот другая возможная ошибка: параметры одни и те же, но в базовом классе метод константный, а в производном — нет.
class B 
{
public:
   virtual void f(int) const {std::cout << "B::f " << std::endl;}
};

class D : public B
{
public:
   virtual void f(int) {std::cout << "D::f" << std::endl;}
};

И снова это две перегруженные, а не переопределенные функции.
К счастью, теперь есть способ избавиться от этих ошибок. Были добавлены два новых идентификатора (не ключевые слова): override, для указания того, что метод является переопределением виртуального метода в базовом классе и final, указывающий что производный класс не должен переопределять виртуальный метод. Первый пример теперь выглядит так:
class B 
{
public:
   virtual void f(short) {std::cout << "B::f" << std::endl;}
};

class D : public B
{
public:
   virtual void f(int) override {std::cout << "D::f" << std::endl;}
};

Теперь это вызовет ошибку при компиляции (точно так же, если бы вы использовали override во втором примере):
'D::f': method with override specifier 'override' did not override any base class methods

С другой стороны, если вы хотите сделать метод, не предназначенный для переопределения (ниже в иерархии), его следует отметить как final. В производном классе можно использовать сразу оба идентификатора.
class B 
{
public:
   virtual void f(int) {std::cout << "B::f" << std::endl;}
};

class D : public B
{
public:
   virtual void f(int) override final {std::cout << "D::f" << std::endl;}
};

class F : public D
{
public:
   virtual void f(int) override {std::cout << "F::f" << std::endl;}
};

Функция, объявленная как final, не может быть переопределена функцией F::f() — в этом случае, она переопределяет метод базового класса (В) для класса D.

#5 — строго-типизированный enum


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

Эти проблемы устранены в С++11 с введением новой категории перечислений, названных strongly-typed enums. Они определяются ключевым словом enum class. Они больше не экспортируют свои перечисляемые значения в окружающую область видимости, больше не преобразуются неявно в целый тип и могут иметь определенный пользователем тип (эта опция так же добавлена и для «традиционных» перечислений").
enum class Options {None, One, All};
Options o = Options::All;


#6 — интеллектуальные указатели


Есть много статей, как на хабре, так и на других ресурсах, написанных на эту тему, поэтому я просто хочу упомянуть об интеллектуальных указателях с подсчетом ссылок и автоматическим освобождением памяти:
  1. unique_ptr: должен использоваться, когда ресурс памяти не должен был разделяемым (у него нет конструктора копирования), но он может быть передан другому unique_ptr
  2. shared_ptr: должен использоваться, когда ресурс памяти должен быть разделяемым
  3. weak_ptr: содержит ссылку на объект, которым управляет shared_ptr, но не осуществляет подсчет ссылок; позволяет избавиться от циклической зависимости

Приведенный ниже пример демонстрирует unique_ptr. Для передачи владения объектом другому unique_ptr, используйте std::move (эта функция будет обсуждаться в последнем пункте). После передачи владения, интеллектуальный указатель, который передал владение, становится нулевым и get() вернет nullptr.
void foo(int* p)
{
   std::cout << *p << std::endl;
}
std::unique_ptr<int> p1(new int(42));
std::unique_ptr<int> p2 = std::move(p1); // transfer ownership

if(p1)
  foo(p1.get());

(*p2)++;

if(p2)
  foo(p2.get());

Второй пример демонстрирует shared_ptr. Использование похоже, хотя семантика отличается, поскольку теперь владение совместно используемое.
void foo(int* p)
{
}
void bar(std::shared_ptr<int> p)
{
   ++(*p);
}
std::shared_ptr<int> p1(new int(42));
std::shared_ptr<int> p2 = p1;
   
bar(p1);   
foo(p2.get());

Первое объявление эквивалентно следующему:
auto p3 = std::make_shared<int>(42);

make_shared — это функция, имеющая преимущество при выделении памяти для совместно используемого объекта и интеллектуального указателя с единственным выделением, в отличие от явного получения shared_ptr через конструктор, где требуется, по крайней мере, два выделения. Из-за этого может произойти утечка памяти. В следующем примере как раз это демонстрируется, утечка может произойти в случае, если seed() бросит исключение.
void foo(std::shared_ptr<int> p, int init)
{
   *p = init;
}
foo(std::shared_ptr<int>(new int(42)), seed());

Эта проблема решается использованием make_shared.
И, наконец, пример с weak_ptr. Заметьте, что вы должны получить shared_ptr для объекта, вызывая lock(), чтобы получить доступ к объекту.
auto p = std::make_shared<int>(42);
std::weak_ptr<int> wp = p;

{
  auto sp = wp.lock();
  std::cout << *sp << std::endl;
}

p.reset();

if(wp.expired())
  std::cout << "expired" << std::endl;

#7 — лямбды


В новом стандарте наконец-то была добавлена поддержка лямбда-выражений. Мы можете использовать лямбды везде, где ожидается функтор или std::function. Лямбда, вообще говоря, представляет собой более короткую запись функтора, что-то вроде анонимного функтора. Подробнее можно почитать, например, на MSDN.
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);

std::for_each(std::begin(v), std::end(v), [](int n) {std::cout << n << std::endl;});

auto is_odd = [](int n) {return n%2==1;};
auto pos = std::find_if(std::begin(v), std::end(v), is_odd);
if(pos != std::end(v))
  std::cout << *pos << std::endl;

Теперь немного более хитрые — рекурсивные лямбды. Представьте лямбду, представляющую функцию Фибоначчи. Если вы попытаетесь записать ее, используя auto, то получите ошибку компиляции:
auto fib = [&fib](int n) {return n < 2 ? 1 : fib(n-1) + fib(n-2);};

error C3533: 'auto &': a parameter cannot have a type that contains 'auto'
error C3531: 'fib': a symbol whose type contains 'auto' must have an initializer
error C3536: 'fib': cannot be used before it is initialized
error C2064: term does not evaluate to a function taking 1 arguments

Здесь имеет место циклическая зависимость. Чтобы избавиться от нее, необходимо явно определить тип функции, используя std::function.
std::function<int(int)> lfib = [&lfib](int n) {return n < 2 ? 1 : lfib(n-1) + lfib(n-2);};

#8 — non-member begin() и end()


Вы, вероятно, заметили, что в примерах ранее, я использовал функции begin() и end(). Это новое дополнение к стандартной библиотеке. Они работают со всеми контейнерами STL и могут быть расширены для работы с любым типом.

Давайте возьмем, например, предыдущий пример, где я выводил вектор и затем искал первый нечетный элемент. Если std::vector заменить С-подобным массивом, то код будет выглядеть так:
int arr[] = {1,2,3};
std::for_each(&arr[0], &arr[0]+sizeof(arr)/sizeof(arr[0]), [](int n) {std::cout << n << std::endl;});

auto is_odd = [](int n) {return n%2==1;};
auto begin = &arr[0];
auto end = &arr[0]+sizeof(arr)/sizeof(arr[0]);
auto pos = std::find_if(begin, end, is_odd);
if(pos != end)
  std::cout << *pos << std::endl;

С begin() и end() его можно переписать следующим образом:
int arr[] = {1,2,3};
std::for_each(std::begin(arr), std::end(arr), [](int n) {std::cout << n << std::endl;});

auto is_odd = [](int n) {return n%2==1;};
auto pos = std::find_if(std::begin(arr), std::end(arr), is_odd);
if(pos != std::end(arr))
  std::cout << *pos << std::endl;

Это почти полностью идентично коду с std::vector. Таким образом, мы можем написать один универсальный метод для всех типов, которые поддерживаются функциями begin() и end().
template <typename Iterator>
void bar(Iterator begin, Iterator end) 
{
   std::for_each(begin, end, [](int n) {std::cout << n << std::endl;});

   auto is_odd = [](int n) {return n%2==1;};
   auto pos = std::find_if(begin, end, is_odd);
   if(pos != end)
      std::cout << *pos << std::endl;
}

template <typename C>
void foo(C c)
{
   bar(std::begin(c), std::end(c));
}

template <typename T, size_t N>
void foo(T(&arr)[N])
{
   bar(std::begin(arr), std::end(arr));
}

int arr[] = {1,2,3};
foo(arr);

std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
foo(v);

#9 — static_assert и классы свойств


static_assert проверяет утверждение во время компиляции. Если утверждение — истина, то ничего не происходит. Если — ложь, то компилятор выводит указанное сообщение об ошибке.
template <typename T, size_t Size>
class Vector
{
   static_assert(Size > 3, "Size is too small");
   T _points[Size];
};

int main()
{
   Vector<int, 16> a1;
   Vector<double, 2> a2;
   return 0;
}

error C2338: Size is too small
see reference to class template instantiation 'Vector<T,Size>' being compiled
   with
   [
      T=double,
      Size=2
   ]

static_assert становится более полезен, когда используется с классами свойств. Это набор классов, которые предоставляют информацию о типах во время компиляции. Они доступны в заголовке <type_traits>. Есть несколько видов классов в этом заголовке: классы-помощники, классы преобразований и непосредственно классы свойств.
В следующем примере, функция add, как предполагается, работает только с целочисленными типами.
template <typename T1, typename T2>
auto add(T1 t1, T2 t2) -> decltype(t1 + t2)
{
   return t1 + t2;
}

Однако, при компиляции не возникнет ошибки, если написать следующее:
std::cout << add(1, 3.14) << std::endl;
std::cout << add("one", 2) << std::endl;

Программа просто выведет «4.14» и «е». Используя static_assert, эти две строки вызовут ошибку во время компиляции.
template <typename T1, typename T2>
auto add(T1 t1, T2 t2) -> decltype(t1 + t2)
{
   static_assert(std::is_integral<T1>::value, "Type T1 must be integral");
   static_assert(std::is_integral<T2>::value, "Type T2 must be integral");

   return t1 + t2;
}

error C2338: Type T2 must be integral
see reference to function template instantiation 'T2 add<int,double>(T1,T2)' being compiled
   with
   [
      T2=double,
      T1=int
   ]
error C2338: Type T1 must be integral
see reference to function template instantiation 'T1 add<const char*,int>(T1,T2)' being compiled
   with
   [
      T1=const char *,
      T2=int
   ]

#10 — семантика перемещения


Это — еще одна важная тема, затронутая в С++11. На эту тему можно написать несколько статей, а не абзацев, поэтому я не буду сильно углубляться.

C++11 ввел понятие rvalue ссылок (указанных с &&), чтобы отличать ссылка на lvalue (объект, у которого есть имя) и rvalue (объект, у которого нет имени). Семантика перемещения позволяет изменять rvalues (ранее они считались неизменными и не отличались от типов const T&).

Класс/структура раньше имели некоторые неявные функции-члены: конструктор по умолчанию (если другой конструктор не определен), конструктор копирования и деструктор. Конструктор копирования выполняет поразрядное копирование переменных. Это означает, что если у вас есть класс с указателями на какие-то объекты, то конструктор копирования скопирует указатели, а не объекты, на которые они указывают. Если вы хотите получить в копии именно объекты, а не лишь указатели на них, вы должны это явно описать в конструкторе копирования.

Конструктор перемещения и оператор присваивания перемещения — эти две специальные функции принимают параметр T&&, который является rvalue. Фактически, они могут изменять объект.

Следующий пример показывает фиктивную реализацию буфера. Буфер идентифицируется именем, имеет указатель (обернутый в std::unique_ptr) на массив элементов типа Т и переменную, содержащую размер массива.
template <typename T>
class Buffer 
{
   std::string          _name;
   size_t               _size;
   std::unique_ptr<T[]> _buffer;

public:
   // default constructor
   Buffer():
      _size(16),
      _buffer(new T[16])
   {}

   // constructor
   Buffer(const std::string& name, size_t size):
      _name(name),
      _size(size),
      _buffer(new T[size])
   {}

   // copy constructor
   Buffer(const Buffer& copy):
      _name(copy._name),
      _size(copy._size),
      _buffer(new T[copy._size])
   {
      T* source = copy._buffer.get();
      T* dest = _buffer.get();
      std::copy(source, source + copy._size, dest);
   }

   // copy assignment operator
   Buffer& operator=(const Buffer& copy)
   {
      if(this != &copy)
      {
         _name = copy._name;

         if(_size != copy._size)
         {
            _buffer = nullptr;
            _size = copy._size;
            _buffer = (_size > 0)? new T[_size] : nullptr;
         }

         T* source = copy._buffer.get();
         T* dest = _buffer.get();
         std::copy(source, source + copy._size, dest);
      }

      return *this;
   }

   // move constructor
   Buffer(Buffer&& temp):
      _name(std::move(temp._name)),
      _size(temp._size),
      _buffer(std::move(temp._buffer))
   {
      temp._buffer = nullptr;
      temp._size = 0;
   }

   // move assignment operator
   Buffer& operator=(Buffer&& temp)
   {
      assert(this != &temp); // assert if this is not a temporary

      _buffer = nullptr;
      _size = temp._size;
      _buffer = std::move(temp._buffer);

      _name = std::move(temp._name);

      temp._buffer = nullptr;
      temp._size = 0;
      
      return *this;
   }
};

template <typename T>
Buffer<T> getBuffer(const std::string& name) 
{
   Buffer<T> b(name, 128);
   return b;
}
int main()
{
   Buffer<int> b1;
   Buffer<int> b2("buf2", 64);
   Buffer<int> b3 = b2;
   Buffer<int> b4 = getBuffer<int>("buf4");
   b1 = getBuffer<int>("buf5");
   return 0;
}

Конструктор копирования по умолчанию и оператор присваивания копии должны быть вам знакомы. Новое в С++11 — это конструктор перемещения и оператор присваивания перемещения, Если вы выполните этот код, то увидите, что когда создается b4 — вызывается конструктор перемещения. Кроме того, когда b1 присваивается значение — вызывается оператор присваивания перемещения. Причина — значение, возвращаемое функцией getBuffer() — rvalue.

Вы, вероятно, заметили использование std::move в конструкторе перемещения, при инициализации имени переменной и указателя на буфер. Имя — это строка std::string и std::string также реализует семантику перемещения. То же самое касается и unique_ptr. Однако, если бы мы записали просто _name(temp._name), то был бы вызван конструктор копирования. Но почему в этом случае не был вызван конструктор перемещения для std::string? Дело в том, что даже если конструктор перемещения для Buffer был вызван с rvalue, внутри конструктора это все равно представляется как lvalue. Чтобы сделать его снова rvalue и нужно использовать std::move. Эта функция просто превращает ссылку lvalue в rvalue.

Вместо заключения


Есть много вещей в С++11, о которых можно и нужно рассказывать; эта статья была лишь одним из многих возможных начал. Эта статья представила серию функций языка и стандартной библиотеки, которую должен знать каждый разработчик С++. Однако, для более глубокого понимания всего сказанного, этой статьи недостаточно, поэтому тут не обойтись без дополнительной литературы.
Блохин Алексей @Renzo
карма
30,2
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (115)

  • 0
    Я не полностью понял move semantic (ночь уже). Можно ли с помощью rvalue сделать swap вообще без какого либо копирования? А вообще хорошая статья, прям С++ радует
    • +5
      template <typename T>
      void swap(T &first, T &second)
      {
          T tmp = std::move(first);
          first = std::move(second);
          second = std::move(tmp);
      }
      


      Если тип T поддерживает move-оператор и move-конструктор (что верно, например, для std::vector), то здесь не будет копирования
      • +1
        Вы практически повторили код std::swap для C++11, кстати она переехала из <algorithms> в <utility>.
        • +2
          Это не удивительно, сложно реализовать std::swap как-то иначе :)
          • –10
            можно xor'ами, там тогда даже временная переменная не нужна, правда это только для простых типов подойдёт )
            • +11
              Да хватит уже. Нельзя swap реализовывать xor'ами даже в случае простых типов, т.к. он не будет работать, если оба аргумента будут ссылками на одну и ту же переменную.
              • –3
                Почему? Просвапает ссылки же. Пусть и 2 одинаковые.
                • +8
                  Вы не поняли, не указатели, а 2 ссылки на одну переменную.
                  void swap(int& a, int& b)
                  {
                      a ^= b;
                      b ^= a;
                      a ^= b;
                  }
                  int x = 3;
                  swap(x, x).
                  // x == 0
                  
            • +17
              Вы правда думаете, что компилятор будет использовать дополнительную переменную в случае простых типов?
              int t = a;
              a = b;
              b = t;
              

              превращается в
              mov eax, [a]
              mov edx, [b]
              mov [b], eax
              mov [a], edx

              А вот
              a ^= b;
              b ^= a;
              a ^= b;
              

              Уже в
              mov eax, [a]
              mov edx, [b]
              xor eax, edx
              xor edx, eax
              xor eax, edx
              mov [a], eax
              mov [b], edx

              Как думаете, что проще и оптимальнее?
              • –8
                А что вы хотели от ответа на риторический вопрос? ;)
                Тем более выше шла речь не о том, насколько оптимально его скомпилит, а о том, как записать по другому.
                • +2
                  Ну, обычно имеется в виду не просто «иначе» записать, а хотя бы столь же оптимально. Потому что способов просто «иначе» можно придумать сколь угодно много.

                  Ну, и выше уже сказали, когда XOR-решение не работает корректно.
                  • +2
                    XOR-решение это вообще из области школьных олимпиад по информатике и вопросов на собеседовании на позицию джуниор С++ программиста: проверяет общее понимание бинарных операций, не вдаваясь в детали и поплёвывая на последствия. В реальной жизни использовать нельзя.
            • 0
              Не всегда можно просто переместить объект в памяти при помощи «лобового» memcpy.
              Например, объект может содержать указатели на свои же поля, или указатель на себя.
              И тогда при прямом перемещении объект станет испорченным.
              Именно поэтому придумали «move semantics» — когда хочется переместить объект, но лобовое перемещение может не сработать.
              Поэтому сделать swap при помощи прямых манипуляций с памятью невозможно. Ни на основе memcpy, ни на основе xor и т. д.
              Можно сделать swap только на основе move или конструктора копирования.
              При чём первый способ быстрее
    • –7
      Когда я в первый раз прочел описание новых фишек, я тоже подумал, что С++ наконец-то радует. Но… нет.
      Foreach? Они наконец-то сделали стандартный foreach (правда, вполне в духе С++)? Господа, на дворе 2013 год! Пока все переползут на этот стандартный foreach со своих кривых макросов, пройдет еще лет 10.
      Auto? Decltype? Это ложка меда в бочке дегтя под названием «синтаксис STL». Теперь, создавая шаблон каких-нибудь матриц, не нужно будет пару дней выводить тип, возвращаемый операцией умножения. Круто, чё.
      Weak_ptr? Они что, шутят? С третьей, или какой там по счету, попытки сделать в STL автоматическое управление памятью, предлагается weak_ptr? Годика через три вопрос «приведите пример, когда нужен weak_ptr» будет встречаться на собеседованиях с такой же частотой, как и вопрос про виртуальное базовое наследование. И с такой же частотой употребляться на практике. Потому что это еще один, тысячный способ гарантированно прострелить себе ногу. А без weak_ptr не будет нормально работать shared_ptr. Ну и зачем тогда всё это?
      Лямбды с явно указываемым списком переменных для замыкания? Это всё уже есть с тех времен, когда в плюсах разрешили перегружать operator(). Ну да, экономится строчек пять кода, и на том спасибо.
      Самое плохое, что С++ идет куда-то не туда. Страуструп и Ко пытаются догнать современные языки по количеству модных плюшек, имея за спиной тяжеленный груз совместимости. В том же Javascript, при всех его недостатках, не нужен ни дикий синтаксис шаблонов, ни явное указание замыканий, ни тем более шаманства с «умными» указателями, которые подвластны только гениям — компиляторы как-то справляются с выводом всего этого автоматически. А главная фишка плюсов — скорость — при использовании всего этого теряется, потому что все эти универсальные штуки, типа std::map, std::sort или там умных указателей — ровно они же и используются в новых языках.
      Как только вы начинаете писать близко к железу и выжимать из него максимум, вы выкидываете весь ооп-мусор, начинаете думать об упаковке данных и в итоге скатываетесь к старому доброму Си, просто с чуть более современным синтаксисом.
      Ну и где тут тогда область применения, в которой С++ будет наголову лучше остальных языков?
      Не вижу.
      • +13
        weak/shared_ptr в бусте обитает 10 лет и все С++ программисты должны были это знать. Про новые фишки — они не такие уж и новые, просто на разработку стандарты некоммерческой организацией ушло много времени. Можете сравнить с Java там тоже декларировано много чего, а с работающей реализацией все не так радужно
      • +3
        Самое плохое, что С++ идет куда-то не туда. Страуструп и Ко пытаются догнать современные языки по количеству модных плюшек, имея за спиной тяжеленный груз совместимости.


        Вы сами и ответили.
      • +2
        Вы не там видите место С++. Нравится Javascipt? Берите Qt, в нём есть прекрасный яваскрипт-подобный QML, есть движок V8 и есть возможность совмещать их гибкость со скоростью и мощью лежащих внизу С++ библиотек. Вьюха на яваскрипте, модель на С++. Хочется быстрее писать — контроллер на яваскрипте, хочется больше контроля над памятью и скоростью — на С++.
      • +1
        На чём написан Хром? ;)
      • 0
        Что же если вы разочаровались в C++ похоже вам дорога на D или Go как сделал Александреску.
        Возможно мне бы хотелось чего-либо большего, но я понимаю что «совместимость с С» (по крайней мере с C89) и design by comitee не дадут ничего революционного, хотя бы уже потому, что не сломать существующий код почти невозможно, а создать New C++ который будет несовместим с предыдущими никто не даст. Упомянутый вами JavaScript имеет те же проблемы. Нельзя сломать совместимость и нужно договариваться с другими членами комитета.
  • +5
    Имхо, auto делает код менее читаемым. Мне как-то более удобно читать полное описание итератора в циклах. Этот сахар скорее для удобства написания кода, но код должен быть удобно читаемым, а не удобно пишущимся (это не всегда одно и то же)
    • +11
      Это скорее дело привычки, на самом деле auto как раз удобнее использовать как раз в качестве замены итераторов.
      Заметно сокращает код и по-моему повышает его читабельность, конечно при условии понимания предыдущего когда и принципа работы итераторов, как таковых
      • +5
        С итераторами соглашусь — обычно это какой-то типичный контейнер, из контекста обычно понятно, о чем идет речь. Но вот повсеместное использование auto очень мешает анализировать код.

        Новый C++, конечно, няша. Увы, из-за использования старых компиляторов, не получается задействовать его плюшки в рабочих проектах, приходится только just4fun проектах.

        За статью спасибо! Очень радуюсь, когда кто-то что-то пишет или переводит о C++, сейчас это не очень популярно.
        • +1
          Спасибо!
          Вопрос на засыпку: «Почему писать про С++ не очень популярно?»
          Я сейчас всего лишь заканчиваю 3 курс, но работаю по специальности, как раз программистом С++ (80% — в Qt). Так вот, когда-то давно, соответственно, вставал вопрос выбора основного языка, начинал изучать Java, но все таки С++ оказался роднее :)
          В то же время я мониторил рынок, спрос на те или иные языки (СПб), и С++ ничем не уступал (в то время (2 года назад), да и сейчас вроде тоже) как Java, так и, например, C#. Это касается и количества предложений, и зарплат.
          Так вот, что такого в С++ плохого, из-за чего следовало бы прямо сейчас продолжить учить джаву или начинать учить C#?)
          • +1
            Прошу прощения, речь шла не о том, что люди не пишут на C++, а о том, что статьи о C++ на Хабре появляются очень редко. Последняя неделя — так вообще праздник, но обычно все как-то кисло.

            В реальности программисты C++ очень ценны и востребованы. Я также, как и Вы, работаю программистом C++ с использованием инструментария Qt. Не жалуюсь :) C++ используется везде и всюду, и если появляется какая-то новая ОС, платформа или что-то еще, с вероятностью 90% она обзаведется поддержкой С++. Так что Ваш выбор правильный.

            Ну а C# или Java есть смысл учить для расширения кругозора. Ну и просто некоторые вещи быстрее и лучше сделать на этих языках.
        • +16
          сейчас это не очень популярно.

          Это вам так кажется, давайте посмотрим:
          Мобильные платформы:
          1. Android — усиленно пилят NDK, потому что игры хотят native, дальвик не справляется
          2. Tizen/Sailfish/Blackberry/UbuntuPhone — С++ основной язык разработки
          3. WindowsPhone — запилили поддержку native (а то есть C++) по многочисленным просьбам
          4. iOS в целом тоже позволяет писать на C++, но это не популярно.

          Не мобильные платформы:
          1. Практически весь софт под Linux написан на C/C++, а учитывая распространение на серверах, это огромное количество кода.
          2. Часть софта на Windows, и практически весь кроссплатформенный софт (который не Java)
          3. Игры, всякие Doom и прочая, потому что проверенно и шустро
          4. Практический любой серьезный софт для рассчетов, систем контроля и т.д.

          Ну и список можно долго продолжать,
          что бы тут не говорили, С++ до сих пор один из наивостребованейших языков,
          как бы не хотелось его смерти всяким неосиляторам.
          • 0
            Как я уже написал в комментарии выше, прошу прощения за свое кривое выражение — я хотел сказать, что статьи на хабре о C++ появляются, увы, очень редко.
            Я сам разработчик С++, использую инструментарий Qt, периодически вижу Ваши полезные советы на форуме Qt :) На жизнь не жалуюсь. И полностью согласен с Вашим сообщением про популярность.
      • –5
        Если честно, изначально итераторы тут ни при чем, и появление auto — это лямбды, когда винегретный набор на выходе. Все прочие рассуждения — бальзаковская лирика.

        IMHO:
        Использование auto / var в обыденных объявлениях лично для меня является генерацией менее человекочитаемого кода, это тоже самое, что использование обратной польской нотации в математических выражениях — вы же ее не используете?
        Кроме того, это расхолащивает внимание кодера к используемым типам, и он становится намного более быдлокодером.

        Если в шарпе холивар вокруг var в обыденном объявлении имеет место быть в тысячах тредов, то такова архитектура языка, поскольку шарп сам по себе естественнее в чтении, а в сочетании с R# или CodeRush вообще расслабляет.

        Но, плюсы итак не просты как шарп, решарпер тут слабый помощник, а 11 делает код еще более замороченным, и еще менее человекочитаемым. Тут бы однозначно поостерегся использовать auto, и рекомендовал бы в код-гайде избегать использования auto везде, кроме лямбды. Лучше сразу щепетильно и дисциплинированно относиться к коду и типизации, чем надеяться на статические анализаторы и огребать на код-ревью.
        • 0
          del
    • +3
      Что-то мне подсказывает, что с auto может быть ситуация аналогичная var в C#.
      Вначале почти все ругаются: да зачем оно надо, кто это будет использовать и тд. Но, со временем, результатом становятся более внятные названия переменных и повсеместный var.
    • +1
      Имхо, auto делает код менее читаемым. Мне как-то более удобно читать полное описание итератора в циклах.

      На самом деле большинство сред разработки прекрасно поддерживают новое описание итератора.
      Например код:
      for ( auto param : params )
      

      Никак не понижает понимание кода, т.к. тип переменной param прекрасно анализируется IDE.
  • +5
    Позвольте чуток поправить:
    B::f — это просто другой метод с тем же именем, перегруженный, а не переопределенный

    И снова это две перегруженные, а не переопределенные функции

    Здесь нет перегрузки, здесь есть скрытие (hide) метода f базового класса B. Чтобы включить перегрузку, нужно добавить в производный класс using B::f;
  • 0
    синтаксис плюсов все больше начинает напомнить java и c#, что, естественно, радует.
    • +2
      Ну с учетом того, когда появилось предложение такого синтаксиса для лямбд — неизвестно кто на кого становится более похож. В новой яве вон вообще :: стали использовать для «указателей на функции»
  • –17
    Я всегда использовал этой ключевое слово так же и в производных классах (и поощрял людей, кто так делал), чтобы код был понятнее.
    И почему же вы тогда поощряете людей использовать дьявольское auto, которое делает код нечитабельным?

    // Или, сравним С++03 и С++11
    // C++03
    for (std::vector<std::map<int, std::string>>::const_iterator it = container.begin(); it != container.end(); ++it)
    {
       // do smth
    }
    // C++11
    for (auto it = container.begin(); it != container.end(); ++it)
    {
       // do smth
    }


    Может, так вам станет понятно, что авто зло:

    
    auto container;
    for (auto it = container.begin(); it != container.end(); ++it)
    {
       // do smth
    }

    понять, что за контейнер может быть очень проблематично.
    • +21
      auto container;
      

      Это даже не скомпилируется. Вы уверены, что да конца понимаете auto?
      • –9
        На С++11 не писал. Читал фичелист, но забыл про необходимость явной инициализации переменной.
    • +4
      В вашем примере вы делаете код «некомпилируемым», а не «нечитаемым»
    • +2
      ну так нужно назвать его не container а
      myFavoritePornSitesArray
      или
      invertedPageRankMap
      и станет понятнее для читающего код, даже с учётом auto.
  • +8
    Каждый раз, когда я вижу на хабре статью с полезной информацией, сразу проматываю в конец. Потому что с вероятностью 90% это просто перевод. А в оригинале читать удобнее и быстрее. Было бы еще лучше, если бы ссылка на оригинал давалась сразу в начале.
  • +2
    Что лично меня забавляет в С++11 — есть make_shared, но нет make_unique. Нет, правда? За много лет работы над стандартном никто не заметил, что они забыли make_unique, и для исправления недостатка им нужен С++14?

    А так стандарт очень клёвый. Практически всё, что пишу на С++ — личные проекты for fun, поэтому «переехал» сразу и ни разу не пожалел. Он и правда feels like a new language.
    • 0
      А если не секрет, основной язык какой?
    • +3
      Не совсем забыли.

      make_unique имела бы тривиальную реализацию (которая сводится к вызову конструктора unique_ptr).
      make_shared же имеет внутри некую «магию», которая выделяет ОДИН кусок памяти под объект и счетчик ссылок вместо ДВУХ кусков памяти, как было бы при вызове конструктора shared_ptr.

      Иными словами make_unique полезна (т.к. безопасена при возникновении исключений), но не необходима (реализуется руками за 5 минут).
      А вот make_shared необходима, т.к. для её реализации (без применения «чорной магии») надо менять внутренности shared_ptr.
      • 0
        Этот «умный» make_shared после того как выделит память одним куском — вызывает placement new? А как эта штука будет сочетаться с кастомным operator new (в том числе и если кастомный оператор написан конкретно для данного класса)?
        • +2
          Да, он вызывает placement new (по сути — конструктор). С перегруженным оператором new такая штука никак не будет сочетаться. Причина простая: оператор new не отвечает за инициализацию объекта, он отвечает только за выделение памяти, а в данном случае память выделяется «особым» образом. Вообще, перегрузка оператора new в С++ — это плохая идея, которая может приводить к трудно отлавливаемым багам. В stl, boost и т.д. за выделение памяти отвечают аллокаторы. Вместо перегрузки операторов нужно использовать аллокаторы. В частности есть функция allocate_shared, которая делает то же самое, что и make_shared, но выделяет память с помощью указанного аллокатора.

          Обратите внимание, в С++ есть 3 механизма выделения памяти:
          1. malloc/calloc/free.
          2. Операторы new/delete, как глобальные так и классовые.
          3. Аллокаторы.

          malloc/calloc/free — это седая древность, наследие С, используется только в недрах реализации new/delete. Не вызывают конструкторы, просто выделяют кусок памяти. Использовать их, конечно, можно… только зачем? Единственным исключением здесь может быть calloc, который выделяет память на стеке (это очень быстро) и не имеет аналогов в С++. Память выделенная calloc автоматически освобождается при выходе за область видимости. Calloc можно использовать при особо экстремальных оптимизациях, если точно знаешь что делаешь.

          С глобальными операторами new и delete все плохо. Если их переопределить, то малейшая неосторожность в порядке #include может вылиться в несоответствие new и delete. Кроме того, new и delete вносят путаницу просто своим существованием. Дело в том, что их реализация (см. исходники stdlib) отличается от malloc/free только тем, что при ошибке выделения памяти кидается исключение. Вы нигде не увидите явный вызов конструктора, а ведь он вызывается! Но вызывается он «за кадром». Когда вы пишете в коде new X(), язык понимает это как «надо вызвать оператор new для выделения памяти, а потом вызвать конструктор». Сама реализация new при этом ничего не знает про конструктор! То же самое с классовыми операторами. Они ничем не отличаются от глобальных, кроме области «применимости» (С++ сначала ищет классовый оператор, а затем, если он не найден, — использует глобальный). Классовые операторы немного безопаснее глобальных, т.к. delete в большинстве случаев будет вызываться правильный (т.к. он определен в хедере класса; исключение — когда вы вызываете delete для неполного типа: в этом случае не вызовется деструктор (плохо) и вызовется стандартный оператор delete — еще хуже). По итогу: new/delete — это все теже malloc/free, только теперь язык еще вызывает конструктор/деструктор, вызывает сам, операторы про это ничего не знают. Перегружать их опасно, может потом стоить «ноги». Их бы вообще выпилить, но они используются аллокаторами.

          С алокаторами все хорогшо :) Аллокаторы — это специальные объекты, которые занимаются выделением и освобождением памяти. Вы можете легко создать несколько независимых аллокаторов, которые никак не будут друг на друга влиять.Если вы знаете, что какой-то алгоритм требует минимума кеш-промахов, вы можете легко выделить всю необходимую ему память с помощью отдельного аллокатора в отдельном физическом адресном пространстве. Стандартные аллокаторы работают внутри через new/delete, однако никто не запрещает им работать через функции операционной системы.

          С памятью почти все, не упоминались только delete[] и векторные аллокаторы, но это уже детали. Заметьте, я ничего не написал про placement new. Дело в том, что placement new это не оператор, это даже не функция, он не имеет реализации. Это просто синтаксический механизм, который говорит компилятору, что нужно вызвать конструктор, используя в качестве this такой-то адрес.
          • 0
            Небольшая поправка.
            calloc — это malloc + зануление выделенной памяти.
            На стеке память выделяет alloca.
            • 0
              согласен, ассоциация выстрелила
  • +1
    Было бы здорово, если бы для каждой фичи указали с какой версии компиляторов её начали поддерживать.
    • +3
      Можно тут глянуть.
  • +2
    Извиние, но то, что тут возможна утечка,
    foo(std::shared_ptr<int>(new int(42)), seed());
    

    если seed выбросит исключение, — не правда.
    1. Если до вычисления 1 аргумента дело не дошло, то и не дойдёт. Утечки нет.
    2. Если сначала будет вычислен умный указатель, то во время исключения в seed будет вызван деструктор этого указателя и объекта. Утечки нет.
    Таким образом, исключение в seed никак не повлияет, всё будет нормально.
    Утечка возможна только в том случае, если исключение произойдёт в конструкторе shared_ptr.
    • 0
      Кста, есть у мя одна непонятка на эту тему. Вот что пишет один уважаемый человек herbsutter.com/2013/05/29/gotw-89-solution-smart-pointers/
      Обрати внимание что он пишет нас счет make_unique и exception safety. По идее с shared_ptr<...>(new ..) те же проблемы, но о них саттер не упоминает. Это Саттер забыл упомянуть или все таки между make_shared и make_unique в этом моменте какие то различия есть?
      • 0
        В этом плане различий нет, имхо, справедливо для обоих смарт-указателей.
        Почему он так выразился:
        Besides symmetry with make_shared, make_unique offers at least two other advantages.
        мне тоже не понятно.

        С точки зрения exception safety между unique_ptr и shared_ptr (без использования make_xxx) есть отличие:
        unique_ptr<T>(p) не бросает исключений, т.е. noexcept;
        shared_ptr<T>(p) может бросить std::bad_alloc при невозможности выделить дополнительную память в куче для счетчика ссылок и может бросать implementation-defined исключения при других ошибках.
    • +1
      Все плюсуют, значит согласны?
      Компилятор вправе выстроить последовательность
      1. new int(42)
      2. seed()
      3. std::shared_ptr()

      Исключение в seed дает утечку. Рекомендую Саттера почитать. Он в «Решение сложных задач на С++» в 2.17 пример приводил, хотя для auto_ptr, но сути не меняет.
      • +2
        Посмотрире внимательней на скобки
        foo(std::shared_ptr(new int(42)), seed());

        Как видите, не может seed() быть вычисленным между двумя этапами инициализации умного указателя.
        • +2
          Оказывается всё же может. В соответствии со стандартом:
          1.
          If A is not sequenced before B and B is not sequenced before A, then A and B are unsequenced. [ Note: The execution of unsequenced
          evaluations can overlap. — end note ] Evaluations A and B are indeterminately sequenced when either A is sequenced before B or B is sequenced before A, but it is unspecified which. [ Note: Indeterminately sequenced evaluations cannot overlap, but either could be executed first. — end note ]

          2.
          When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [ Note: Value computations and side effects associated with different argument expressions are unsequenced. — end note ]

          Так что получается, что компилятор имеет право разделить вычисление выражения аргументов и перемешать их.
        • 0
          Может. Не вдаваясь в дебри стандарта, вот простой пример от Саттера, Задача 2.17. Проблема неуправляемых указателей. Часть 2 и ее решение
          • 0
            Поэтому рекомендуют использовать make_xxx_ptr везде, где это возможно, и не ломать голову насчет потенциальных утечек. Так проще и надежнее.
      • +1
        Да, почитал внимательнее стандарт, там для функций сказано:
        Value computations and side effects associated with different argument expressions are unsequenced.

        Был уверен, что indeterminately sequenced. Спасибо.
  • 0
    Осознал, что пренебрегаю написанием override в наследниках.
    Возник вопрос — насколько целесообразно override'ить методы, переопределяющия абсолютно виртуальные методы?

    class B 
    {
    public:
       virtual void f(int) = 0;
    };
    
    class D : public B
    {
    public:
       virtual void f(int) override {std::cout << "D::f" << std::endl;}
    };
    


    Если я ошибусь с типом в перегруженном методе — компилятор все-равно выдаст «Cannot initialize Abstract class», так что override кажется избыточным. С другой стороны, зачем мне сделить абсолютно ли виртуальный у меня базовый метод? Просто взять в привычку всегда писать override в наследниках?
    • +4
      override — хорошая (обратная) проверка с помощью компилятора того, что вы делаете в плане иерархии, наследования и т.д. Хуже не будет, мне лично нравится.
    • +1
      По override Вы получите с каким методом проблема, а по abstract class — не факт (добрая воля компилятора).
    • 0
      К тому же вы можете сменить чисто виртуальный метод на обыкновенный, и эта ошибка уже может ускользнуть.
  • +2
    Ключевое слово virtual опционально и поэтому немного затрудняло чтение кода, заставляя вечно возвращаться в вершину иерархии наследования, чтобы посмотреть объявлен ли виртуальным тот или иной метод
    Любая приличная IDE выделяет виртуальные методы, независимо от использования ключевого слова virtual в наследниках.
    • 0
      Я не знаток приличных IDE для С++, не подскажете пару штук? Visual Studio, насколько я помню, не умеет такого.
      • +2
        Студия начиная с 2008 насколько я помню такое делала по дефолту. Честно говоря давно ей не пользуюсь, в основном QtCreator.
      • +2
        Eclipse+CDT выделяет.
      • 0
        Qt Creator.
        VisualAssist для VisualStudio, тоже позволяет выделить.
  • НЛО прилетело и опубликовало эту надпись здесь
    • +2
      Поправьте меня, если я не прав, но такие константы стоит объявлять статическими, а запись
      class Foo
      {
        static const int myConst = 0xB00B5;
      };
      
      была возможна и в С++03.
      • 0
        Немного поправлю себя же. Такая запись допустима только для простых (или даже интегральных, не помню точно) типов. А в новом стандарте можно и объекты так инициализировать.
      • НЛО прилетело и опубликовало эту надпись здесь
    • +1
      А вот для неконстантных членов — это действительно приятная возможность избежать дублирования умолчательного значения в ворохе конструкторов.
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          В чём смысл? В списке? Ну, для новичка в C++ или C++11 — новые приятные штучки. Без «страшных» деталей.
  • 0
    В последнем листинге, кстати, было выяснено (но не известно точно почему), что:
    assert(this != &temp); // assert if this is not a temporary
    
    не имеет смысла, т.к. this все равно может оказаться temp, а проверить это нет возможности.
    Необходимо самому сделать следующее:
    if(this != &temp)
    {
      // 1. освобождаем ресурсы, занятые этим объектом
      _buffer = nullptr;
      // 2. берем данные из temp
      _size = temp._size;
      _buffer = std::move(temp._buffer);
      _name = std::move(temp._name);
      // 3. присванивание чего-либо temp будет препятствовать тому, чтобы деструктор
      // освободил ресурсы, ранее занимаемые этим объектом
      // temp._buffer = nullptr; <-- нет необходимости из-за использования std::move
      temp._size = 0; // <-- в этом, соответственно, тоже нет необходимости
    }
    return *this;
    
  • 0
    Можно ли при объявлении типа функции как auto объявить рекурсивную функцию, например факториал?
    • 0
      auto fact(long long n)
      {
          return (n == 1)? 1: n * fact(n - 1);
      }
      

      Я думаю вполне должно работать, хотя надо проверить. При объявлении функции типа auto, компилятор будет искать нужный тип в возвращаемом значении функции, умножение этому помещать не должно. Хотя, если будут проблемы, то, возможно, понадобится как-то прикрутить decltype
      • 0
        Да, конечно же, надо использовать decltype, следующий код рабочий:
        auto fact(long long n) -> decltype(n)
        {
            return (n == 1)? 1: n * fact(n - 1);
        }
        
      • 0
        Будет в С++14.
  • 0
    В комментариях выше, подкинули идеи использовать swap, оператор присваивания копии в последнем примере можно переписать следующим образом:
    Buffer& operator=(Buffer copy)
    {
         swap(*this, copy);
         return *this;
    }
    // ...
    void swap(Buffer& first, Buffer& second) noexcept
    {
         using std::swap;
         swap(first._name  , second._name);
         swap(first._size  , second._size);
         swap(first._buffer, second._buffer);
    }
    
  • +1
    Статья интересная, спасибо, но я бы упомянул, что половина «возможностей С++11» — это не возможности вовсе, а расширения стандартной библиотеки.
    В Википедии всё категоризовано — изменения в языке и в библиотеке.
    • +1
      Стандартная библиотека — это как бы часть языка.
      • 0
        Библиотека на то и библиотека, что её использование опционально. И если кто-то хочет использовать собственные шаблоны, алгоритмы и так далее, то и «умные» указатели ему придётся изобретать самому, а автоматические переменные или, допустим, строго-типизированные перечисления никуда не денутся.

        upd: ну, а вообще забейте, я не собирался никому читать лекции. Просто заголовок бросился в глаза :)
    • 0
      Расширение стандартной библиотеки так же является и расширением языка, разве нет? Другое дело, когда меняется какой-то синтаксис или семантика или же добавляются принципиально новые функции/возможности — по этим признакам классифицировать можно
      • 0
        В C++ очень мало «батареек» в стандартной библиотеке. И практически вся стандартная библиотека — всего лишь поддерживает те или иные механизмы именно языка. Поэтому, например, стандарт языка «знает» про utf-8, но стандартной функции по преобразованию utf8-строки в u32string или u16string и наоборот — нет.

        В частности, почти все изменения библиотеки в С++11 отражают изменения в самом языке: variadic templates, constexpr, auto и т.п.
        • 0
          P.S. А ещё эту ситуацию можно трактовать так, что возможности языка улучшали, что бы можно было пользоваться ими с помощью стандартной библиотеки.
  • –1
    Тока код поправь

    std::map<std::string, std::vector<int>> map;
    for(auto it = begin(map); it != end(map); ++it) 
    {
       // do smth
    }
    


    должно быть

    std::map<std::string, std::vector<int>> map;
    for(auto it = map.begin(); it != map.end(); ++it) 
    {
       // do smth
    }
    
    • +1
      Нет, в коде все правильно, здесь используются новые non-member функции (из 8 пункта) — std::begin и std::end
      • 0
        извиняюсь, дочитал не до конца.
      • 0
        А в чем они отличаются от одноимённых методов? Плюсы есть какие-нибудь?
        • +2
          Одинаково работают для контейнеров и для массивов.
        • +2
          Кроме того, что работают для обычных массивов, их можно переопределить для какого-нибудь класса, не меняя его, например, если класс принадлежит библиотеке, поменять код которой проблематично.
  • +1
    В примере про лямбда-выражения ошибку при компиляции примера предлагается решить заменой левой части выражения с auto на std::function. А на самом деле можно сделать понятнее:

    auto fib = [&fib](int n) -> int {return n < 2 ? 1 : fib(n-1) + fib(n-2);};
    


    Да, синтаксис непривычный, но в таком виде можно явным образом определить тип возвращаемого лямбдой значения. Во многих случаях сильно экономит время и нервы.

    Кстати, важное дополнение: компилятор не всегда будет выводить тип возвращаемого значения лямбда-функции по левой части выражения даже тогда, когда она задана в явном виде. Например, компилятор не выведет его, если внутри лямбды есть несколько точек возврата с условным оператором и разными типами.
  • +2
    Мне кажется, стоить добавить, что auto поддерживает модификаторы.

    Примеры:
    const auto &rc = MyFunc1();
    auto *p = MyFunc2();

    Очень удобно, позволяет явно указать семантику переменной без указания конкретного типа.
  • 0
    Они больше не экспортируют свои перечисляемые значения в окружающую область видимости, больше не преобразуются неявно в целый тип и могут иметь определенный пользователем тип (эта опция так же добавлена и для «традиционных» перечислений").


    Что вам мешало привести еще подобный пример?

    #include <iostream>
    
    enum class __enum : int { one = 0 };
    
    int main () {
        std::cout << static_cast<int>(class __enum::one) << std::endl;
        
        return 0;
    }
    


    Почему нет ни слова про union и final в таком контексте?

    #include <iostream>
     
    union __union final {
        const int i;
    };
     
    int main ()  {
        using foo = const union ::__union;
        foo un = { .i =  0 };
     
        std::cout << un.i << std::endl;
     
        return 0; 
    }
    


    Также вы наверное забыли про спецификаторы delete, default:

    struct  __struct final {
        __struct (const value&&) = default;
        __struct (const value&) = delete;
    };
    


    А как же constexpr?

    #include <iostream>
     
    constexpr int foo() {
        return 1;
    }
     
    int main() {
        int array[foo() + 1] = {0, 1};    
     
        std::cout << std::boolalpha << (array[0] < array[1]) << std::endl;
     
        return 0;
    }
    


    Неужели было так сложно привести реализацию std::make_unique?

    namespace std {
    	template <typename __type, typename ...__arguments>
    		inline std::unique_ptr<__type> make_unique_helper (std::false_type, __arguments &&...arguments)
    		{
    		  return std::unique_ptr<__type>(new __type(std::forward<__arguments>(arguments)...));
    		}
    	
    	template <typename __type, typename ...__arguments>
    		inline std::unique_ptr<__type> make_unique_helper (std::true_type, __arguments &&...arguments)
    		{
    			using unique = typename std::remove_extent<__type>::type;
    	
    			return std::unique_ptr<__type>(new unique[sizeof...(__arguments)] {
    				std::forward<__arguments>(arguments)...
    			});
    		}
    	
    	template <typename __type, typename ...__arguments>
    		inline std::unique_ptr<__type> make_unique (__arguments &&...arguments) {
    			return make_unique_helper<__type>(std::is_array<__type>(),
    				std::forward<__arguments>(arguments)...);
    		}
    }
    


    Почему ни слова про std::move_if_noexcept, std::forward, declval.

    Раз уж подняли тему про лямбда-выражения почему не упомянули о том что std::unary_function, std::binary_function и пр. то что в С++11 deprecated.

    template <typename T>
    struct find_equal : std::binary_function <T, T, bool>
    {
    	find_equal(const T &value) : value(value) {};
    
    	bool operator()(const T &left, const T &right) const {
    		return left == value && right == value;
    	}
    
    	private:
    		T value;
    };
    
    file.erase(std::unique(file.begin(), file.end(), ::find_equal<char>(' ')), file.end());
    
    


    Теперь вместо такого нагромождения можно писать так:

    file.erase(std::unique(file.begin(), file.end(), [](const char &left, const char &right) {
         return left == value && right == value;
    }), file.end());
    


    :)
    • +1
      Я надеюсь вы понимаете, что и это не все возможности С++11? Я в самом начале статьи упомянул, что
      существует много новых дополнений к языку и стандартной библиотеке, эта статья лишь поверхностно охватывает часть из них.
      Обо всех возможностях можно книгу писать, в статью уж точно не уместится
      • +1
        Я понимаю что речь шла только о десяти нововведениях, но почему не описать их более подробно и качественно?
        Просто таким макаром можно и в википедии прочитать обзор.

        Не поймите меня не правильно, но я лишь хочу чтобы технические статьи содержали не только красивые загловки, но хороший материал от которого можно будет потом отталкнуться в работе.
    • 0
      * Опечатался в первом примере, указал лишний спецификатор class должно быть так: __enum::one
    • +2
      enum class __enum: int { one = 0 };
      union __union final {
      struct __struct final {
      template <typename __type, typename ...__arguments>


      Хозяйке на заметку:
      Each name that contains a double underscore _ _ or begins with an underscore followed by an uppercase letter (2.12) is reserved to the implementation for any use.

      [global.names]/1.

      Чтобы поучать других в таком тоне, нужно научиться не допускать ошибок самому.
      • 0
        Ага, дурная привычка (изучение исходников STL дает о себе знать).
  • 0
    Функция, объявленная как final, не может быть переопределена функцией F::f() — в этом случае, она переопределяет метод базового класса (В) для класса D.

    Не скомпилится. И это хорошо. Потому что иначе б смысла от final не было.
  • 0
    Я пока только учусь писать на C++, и сразу возникла мысль, возможно глупая. Может ли auto избавить от написания перегруженных функций?
    • +1
      Нет. Избавить от написания перегруженных функций иногда могут шаблоны.
    • 0
      В качестве аргумента функции нельзя указать переменную типа auto.
  • –1
    На мой взгляд auto — очень не аккуратная вещь.
    Его рекомендуют использовать только при объявлении лямбды.

    Гораздо проще сделать хедэр в котором написано
    ///...
    typedef unsigned char            T_1b;          /* 1 byte unsigned */
    typedef unsigned short           T_2b;          /* 2 byte unsigned */
    typedef unsigned int                T_4b;          /* 4 byte unsigned */
    typedef unsigned long long      T_8b;          /* 8 byte unsigned */
    

    и т.д.
    • 0
      Заменять, например, unsigned long long на auto никто и не советовал, размер кода это сократит, а вот читаемости поубавит, typedef'ами в таких случаях, по-моему, пользуются из покон веков. В Qt, к примеру, об этом изначально позаботились, и ранее обозначенный тип записывается как quint64.
      auto — это, в первую очередь, для работы с итераторами (ну и с лямбдами, разумеется)
      • 0
        В вашем же примере:
        auto i = 42;        // i - int
        auto l = 42LL;      // l - long long(!)
        auto p = new foo(); // p - foo*
        

        • +1
          Жто скорее в качестве примера, для наглядности.
    • +1
      Почему не аккуратная? Кто рекомендует?
      Тот же Herb Sutter советует use auto wherever possible. А дядька знает, о чем говорит.
    • 0
      Такой хедэр уже есть, называется cstdint.
      • 0
        Да, я слышал о чём-то подобном, но всё-равно использую свой. Удобно. Типы короткие (4 символа) и понятная логика названий.
        • +1
          Логика как раз отсутствует. Просто Вы привыкли. Вот как можно догадаться, что означает T_1b? При том, что для обозначения типа в C/C++ принято _t (size_t, ptrdiff_t, ...). Далее, «беззнаковость» типа из названия также совершенно неочевидна.
          • 0
            У меня логика есть. У меня тип без знаковый если не написано иначе(знаковые S_1b и т.п.).
            T_1b означает Type 1 Byte
            P. S. Давайте не будем спорить о вкусах.
            • +1
              Давайте не будем спорить о вкусах

              О вкусах и не думал спорить. Я ведь сказал выше о привычке.

              Просто стандарт — это общепринятое, даже если для кого-то было это неочевидно, то после знакомства это становится очевидным. А вот увидев в коде T_1b или S_1b я буду дестабилизирован.
  • –4
    В догонку весьма любопытная статья на счет того, почему С++ не имеет будущего. 70% аргументов чистый троллинг, однако оставшиеся 30 вполне правомерны dlang.ru/10-prichin-pochemu-u-cpp-net-budushhego
  • 0
    Минорная ошибка в #10 — rvalue в С++98/03 — то от чего нельзя взять адрес. Хороший контрпример то что является rvalue, но имеет имя «this».

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.