Pull to refresh

Comments 42

В начале декабря прошлого годя я обращался в ДМК Пресс, где издавался перевод предыдущих книг Майерса, по поводу издания новой книги. Мне ответили, что лицензия куплена, и через несколько месяцев выйдет перевод книги. Так что скоро будет и на русском.
Лучше по чаще себя пинать в плане изучения английского. Для программера знание английского достаточно выгодный навык
Спасибо за совет, но английским я владею. Проблема в том, что я люблю читать аналоговые книги и не люблю платить за 336 страниц 44$ или почти 3000 рублей (текущая цена без доставки на Amazon). А еще большая проблема в том, что ни ваш, ни этот мой комментарии не имеют никакого отношения к топику.
Из всех книг по программированию, которые я пытался читать в переводе на русский, буквально несколько переводов были действительны хороши. В остальных либо куча ошибок, либо очень тяжело читается, потому что переводчики не смогли сохранить стиль автора и текст превратился в какое-то нагромождение терминов.
Не то что содержание книг, даже названия книг порою так переводят, что диву даешься. Пример «The Art of Unit Testing: with Examples in .NET» на русский перевели так: «Искусство автономного тестирования с примерами на С#».
Регистрируетесь на сайте Oreilly и пользуетесь кодом на электронную книгу 50% для всех мемберов.
Могу и поделиться, пишите в личку. Ценю Oreilly за DRM.
Ну и русский тоже можно освежить в памяти ;)
Тут Вы правы. Знать хорошо свой родной язык еще более полезный навык. Вечно то запятые не знаю как правильно расставить, то букву не ту напишу. :)
Ну как минимум рядом с override должен быть еще и final.
Совершенно верно, но Майерс почему-то о нем не упоминает, ну и я не стал
В книге рассказываются нововведения и в C++14, например, auto в параметрах лямбда. Хотя и вы начали аннотацию с " От С++98 к С++11/14. Галопом по всем", про C++14 Вы не писали.

PS отличная идея рассказать о такой книге!
Более того, по сути выражение std::iterator_traits<It>::value_type — не более чем гениальный костыль, придуманный на заре STL для определения типа получающегося при разыменовании итератора, первый вариант будет работать только с типом для которого определена специализация iterator_traits<>, а вот для второго нужен лишь operator*().
Немного неточно. Во-первых, при разыменовании итератора получается ссылочный тип (T&), а value_type — это тип с отброшенной ссылкой (T). Во-вторых, у std::iterator_traits<It> есть реализация по умолчанию, в которой std::iterator_traits<It>::value_type равен It::value_type. А нужен этот вспомогательный класс затем, что у обычного указателя (T*) не может члена-типа данных value_type, поэтому стандартная библиотека предоставляет для указателей специализацию данного класса.

Эта коллизия может произойти с любым кодом уже сейчас, по правилам С++11
std::vector(10, 20) создает обьект из 10 элементов, тогда как
std::vector{10, 20} создает обьект только из двух элементов.
Этот пример вроде неплохо демонстрирует, почему оно так работает. Проблема инициализации через фигурные скобочки в том, что она делает два дела одновременно: предоставляет синтаксис для инициализации с помощью std::initializer_list и работает как универсальная инициализация с контролем над сужением, которая может быть использована в любом месте, где синтаксисом языка допускается инициализация.

В C++98 для создания такой конструкции MyAllocList пришлось бы обьявить шаблонной структурой, продекларировать тип внутри нее и использовать вот так:
MyAllocList<Widget>::type lw;

Надо учитывать, что такой подход дает большую гибкость, потому что шаблон через using нельзя специализировать. Если брать пример с iterator_traits, то мы сможем написать так:
template<typename It>
using iterator_value_type = It::value_type;
но не сможем потом его специализировать, чтобы он работал и для обычных указателей.

default — этот модификатор заставляет компилятор генерировать автоматические функции класса
Тут есть один неприятный момент: если компилятор не cможет эту функцию сгенерировать, то он не выдаст вам сообщение об ошибке, а просто втихушку не станет её генерировать. Скажем вот такой код отлично компилируется:
struct T {
    std::unique_ptr<int> p; // unique_ptr не имеет конструктора копирования
    T(const T&) = default;  // поэтому компилятор не может сгенерировать этот конструктор для T
};
а ошибку вы получите, только когда попытаетесь данный конструктор использовать.

Однако один нюанс все-таки существует — decltype возвращает ссылку для всех выражений отличных от просто имени
Это неправда, например:
std::string f();
decltype( f() ) s; // s будет иметь тип std::string

decltype работает так: если ей передать имя переменной или выражение доступа к члену данных (например, a->b.c) без скобочек, то тип будет совпадать с типом переменной или члена данных. Иначе это считается выражением, а любое выражение имеет свой нессылочный тип (назовем его T) и value category (не знаю, как это правильно перевести). В соответствии с value category добавляется ссылочность: для lvalue результат будет T&, для xvalue — T&&, для prvalue — T.
Со всем согласен, спасибо за отличный разбор.
For lvalue expressions more complicated than names, however, decltype ensures that the type reported is always an lvalue reference

Я долго мялся над этой фразой.

>>delete — призвано заменить старый (и красивый) трюк с приватным обьявлением конструктора по умолчанию и оператора присвоения
Я правильно понимаю, что авторе рекомендует теперь вместо boost::noncopyable использовать способ с delete?
Да, совершенно верно, boost::noncopyable это просто надстройка над приватным конструктором.
По желанию, на мой взгляд. Внутри noncopyable, boost использует тот же самый delete, если собирать с поддержкой C++11:
#if !defined(BOOST_NO_CXX11_DELETED_FUNCTIONS)
      noncopyable( const noncopyable& ) = delete;
      noncopyable& operator=( const noncopyable& ) = delete;
#else
  private:  // emphasize the following members are private
      noncopyable( const noncopyable& );
      noncopyable& operator=( const noncopyable& );
#endif
Просто не могу определиться по этому вопросу. Есть вопрос нна stackoverflow и ответ весьма двояк. С одной стороны да, для новичка delete будет понятен, но для большинства программеров, для которых boost как «отче наш» вполне возможно будет noncopyable понятнее
Вопрос действительно неоднозначный, исходя из того что boost продвигал новый функционал из TR1 подручными средствами (и слава ему за это), а у стандарта руки развязаны, у них должно получиться лучше. Технически оба варианта работают одинаково, вопрос только в выразительности.
Я вообще против канонизации библиотек, boost проделал огромную работу и все его экспериментальные разработки вошли в стандарт, пусть теперь дальше целину пашут. Лично я стараюсь везде где можно использовать std:: вместо boost::
Dura standard sed standard
Эх, к сожалению, многим прграммистам, работающим в больших корпоративных проектах, еще долго не светит не только С++14, но и С++11. Так что остается только облизываться.
Так никто же не мешает завести свой небольшой проект на C++11/14 или поддерживать уже существующие.
Ну если бы кто-нибудь мне заплатил, я бы и завел, и поддержал. :)
тогда облизывайтесь бесплатно на здоровье
Инвестиции в себя. Вам заплатят (ваш работодатель, новый работодатель, заказчик и т.п.), после того, как вы докажите что владеете определёнными технологиями, показав, например активный профиль на гитхабе. Да и кроме того, новые технологии это же просто интересно (по крайней мере для меня).
UFO just landed and posted this here
В этом случае точный тип derefUPLess известен только компилятору, его просто невозможно сохранить в переменной не используя auto. Конечно возможно написать так:
Если лямбда ничего не захватывает (квадратные скобочки пустые), то её можно преобразовать в указатель на функцию:
bool (*derefUPLess)(const std::unique_ptr<Widget>&, const std::unique_ptr<Widget>&) =
    [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2)
        { return *p1 < *p2; };

Выход простой, если в классе Widget перемещающий оператор присвоения продекларирован как noexcept, обьекты будут перемещаться, если нет — копироваться.
А если у Widget перемещающий конструктор не noexcept, но копирующего конструктора нет, то все равно перемещаться. Эту функциональность реализует std::move_if_noexcept()
unless copy constructor is not available, in which case move constructor is used either way and the strong exception guarantee may be waived

Очень сомнительное место. Да, gcc-4.8 работает именно так, но вектор портится напрочь. Я бы так писать никому не советовал, интересно как авторы стандарта это мотивируют?
код для поиграться
#include <atomic>
#include <vector>
#include <iostream>
#include <stdexcept>

struct A
{
	A() : i(++cnt) {}
	A(const A&) =delete;
	A(A&& x) : i(x.i) {
		std::cerr<<"A("<<i<<") is moved at "<<cnt<<"\n";
		x.i=0;
		if(++cnt > 7)
			throw cnt;
	}
	int key() const { return i; }
	static int cnt;
	int i;
};
int A::cnt=0;

main()
{
	std::vector<A> v;
	std::cout<<v.capacity()<<std::endl;
	
	for(int i=0; i < 5; ++i) {
		try {
			v.push_back(A{});
		} catch(int x){ std::cerr<<"bang#"<<x; std::cerr<<std::endl; }
		std::cout<<v.size()<<": ";
		for(const auto& a : v) std::cerr<<a.key()<<" "; std::cerr<<std::endl;
	}
	
	return 0;
}
	

но вектор портится напрочь
Ну уж не напрочь, вектор останется старой длины, просто часть его элементов будет смувлена. C ним даже работать дальше можно (добавлять/удалять элементы и т.д.). Хотя стандарт ничего этого не гарантирует:
Otherwise, if an exception is thrown by the move constructor of a non-CopyInsertable T, the effects are unspecified.

интересно как авторы стандарта это мотивируют?
А что делать то предлагаете, если копирующего конструктора нету, а перемещающий не noexcept? Ошибку компиляции выдавать?
Вектор не портится конечно, технически он остается той же длины, но элементы вектора перемещающим оператором переведены в какое-то неопределенное состояние, технически наверное валидное, но логически совершенно бессмысленное. Представьте себе что элементы вектора — виджеты связанные отношениями родитель-дитё, имеюшие размер, другие параметры, и вдруг часть из них превращается в неинициализированные обьекты, такие какими их возвращает дефолтный конструктор? Что на экране-то появится?
Ошибку компиляции выдавать?

Естественно ошибку! Копировать не можем, мувать я считаю в такой ситуации тоже не имеем права, значит ошибка.
У вас же ещё и исключение наверх выбросилось, если вы его поймав, спокойно продолжаете с массивом работать, то это ваши проблемы. Главное, что вектор будет находиться в состоянии, достаточном для вызова его деструктора (хотя стандарт даже этого не гарантирует). Всё-таки гораздо чаще нужно не strong exception safety, а просто кинуть исключение, чтобы оно там где-то снизу по стеку поймалось. Плюс, если исключение кидается при добавлении одного элемента в конец массива и не происходит реаллокация, то мы вполне можем откатиться в предыдущее состоянии (хотя, опять же, стандарт не говорит, что это произойдет), а на этапе компиляции этого не выяснить.

Ну и выдачу ошибки компиляции в этом случае можно самому прикрутить к вектору строчек в 20 кода, а наоборот было бы сделать проблематично.
если вы его поймав, спокойно продолжаете с массивом работать, то это ваши проблемы

Как так? Исключение для того и ловится чтобы продолжать работать, вся раскрутка стека именно для этого и придумана. Если нужны только исключения которые завершают работу, так это abort() называется.
Проблема с вектором именно в том что исключение может выбросить какой-то из средних элементов, когда часть уже перемещена в новый буфер, а часть еще осталась на старом месте.
По-моему, хватит уже на эту тему, все равно этот кусочек явно не додумали. Вот если можете прислать ссылку на то место где об этом прямо в стандарте говорится, буду благодарен. Хочу на этом месте закладку иметь.
На место, откуда цитата из стандарта? 23.3.6.5p1
наконец-то стандарт признал что существующая в С++98 спецификация исключений неэффективна, признал ее использование нежелательным (deprecated)


А вот это кто-нибудь может пояснить? Типа исключения выбрасывать теперь неправильно? Или о чём это?
Вот, нашёл с ходу: en.wikibooks.org/wiki/C++_Programming/Exception_Handling#Exception_specifications
Речь не о самих исключениях, а об указании, что функция может выбросить только некоторый набор конкретных исключений (или не выбросить вообще — именно это и оставили, изменив синтаксис и поведение).
По описанию очень мутная фича. Ни разу не встречал в реальном коде.
Похоже на checked exceptions в Джаве (тоже плохая идея). Спасибо.
UFO just landed and posted this here
Ничего не имею против книги, но «C++» и «Modern» в одном предложении?
А что вы думаете о словосочетании Modern Perl?
Ну хорошо еще что вы против «Effective» ничего не имеете.
Вы таки не поверите, но Фортран тоже может быть «modern»
UFO just landed and posted this here
Тогда уж стоит ещё упомянуть, что помимо спецификатора noexcept, есть ещё и compile-time оператор c тем же именем, который принимает на вход выражение и возвращает false, если в составе этого выражения есть хоть один вызов функции без noexcept спецификатора, и true в противном случае (более подробно о правилах можно прочитать здесь):
void f() noexcept;
void g();

cout << noexcept(f()); // true
cout << noexcept(g()); // false
cout << noexcept(true ? f() : g()); // also false

Такая встроенная примитивная compile-time проверка на то, может при вычислении выражения броситься исключение или нет. Можно использовать в сочетании со спецификатором noexcept:
void use_f() noexcept( noexcept(f()) ) // noexcept(true)
void use_g() noexcept( noexcept(g()) ) // noexcept(false)
Sign up to leave a comment.

Articles