| Blog |

Free PNGs

Стиль Программирования на C++ Бьерна Страуструпа и Часто Задаваемые Вопросы по Технике

Перевод статьи - Bjarne Stroustrup's C++ Style and Technique FAQ

Автор(ы) - Bjarne Stroustrup

Источник оригинальной статьи:

https://www.stroustrup.com/bs_faq2.html

Изменено 22 ноября 2019г.

Люди часто задают мне вопросы о стиле программирования и технике C++. Если у вас есть вопросы получше или комментарии к ответам, пишите мне по электронной почте (bs at cs dot tamu dot edu). Пожалуйста, помните, что я не могу тратить все свое время на улучшение своей домашней страницы.

Я внес свой вклад в новый, единый, isocpp.org Часто Задаваемые Вопросы (ЧЗВ) по C++, поддерживаемые C++ Foundation, где я являюсь директором. Ведение этого ЧЗВ, вероятно, будет становиться все более спорадическим.

Для получения более общих вопросов, см. общие ЧЗВ.

Для терминологии и концепции, см. глоссарии C++.

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

Темы:

Введение:

Классы:

Иерархии классов:

Шаблоны и общее программирование

Память

Исключения:

Другие особенности языка

Мелочи и стиль

Как я пишу очень простую программу?

Часто, особенно в начале семестров, я получаю много вопросов о том, как писать очень простые программы. Как правило, задача, которую необходимо решить, состоит в том, чтобы прочитать несколько цифр, сделать с ними что-то и написать ответ. Вот пример программы, которая делает это:

	#include
	#include
	#include
	using namespace std;

	int main()
	{
		vector v;

		double d;
		while(cin>>d) v.push_back(d);	// read elements
		if (!cin.eof()) {		// check if input failed
			cerr << "format error\n";
			return 1;	// error return
		}

		cout << "read " << v.size() << " elements\n";

		reverse(v.begin(),v.end());
		cout << "elements in reverse order:\n";
		for (int i = 0; i

Вот несколько наблюдений об этой программе:

	#include
	#include
	#include
	#include
	using namespace std;

	int main()
	{
		vector v;

		double d;
		while(cin>>d) v.push_back(d);	// read elements
		if (!cin.eof()) {		// check if input failed
			cin.clear();		// clear error state
			string s;
			cin >> s;		// look for terminator string
			if (s != "end") {
				cerr << "format error\n";
				return 1;	// error return
			}
		}

		cout << "read " << v.size() << " elements\n";

		reverse(v.begin(),v.end());
		cout << "elements in reverse order:\n";
		for (int i = 0; i

Дополнительные примеры того, как использовать стандартную библиотеку для выполнения простых задач, см. в главе " Экскурсия по Стандартной Библиотеке" TC++PL4.

Можете порекомендовать стандарты кодирования?

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

Не используйте стандарты кодирования C (даже если они слегка изменены для C++) и не используйте стандарты кодирования C++ десятилетней давности (даже если они хороши для своего времени). C++ является не (просто) C, а стандартный C++ - это не (просто) предстандартный C++.

Почему мои компиляции занимают так много времени?

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

Рассмотрим классический пример объектно-ориентированной программы:

	class Shape {
	public:		// interface to users of Shapes
		virtual void draw() const;
		virtual void rotate(int degrees);
		// ...
	protected:	// common data (for implementers of Shapes)
		Point center;
		Color col;
		// ...
	};

	class Circle : public Shape {
	public:	
		void draw() const;
		void rotate(int) { }
		// ...
	protected:
		int radius;
		// ...
	};

	class Triangle : public Shape {
	public:	
		void draw() const;
		void rotate(int);
		// ...
	protected:
		Point a, b, c;
		// ...
	};	


Идея заключается в том, что пользователи манипулируют фигурами через общедоступный интерфейс Shape и что разработчики производных классов (таких как Circle и Triangle) совместно используют аспекты реализации, представленные protected членами.
С этой простой идеей связаны три серьезные проблемы:

Таким образом, наличие "информации, полезной для разработчиков" в базовом классе, который также действует как интерфейс для пользователей, является источником нестабильности в реализации, ложной перекомпиляции пользовательского кода (при изменении информации о реализации) и избыточного включения файлов заголовков в пользовательский код (потому что "информация, полезная для разработчиков" нуждается в этих заголовках). Это иногда называют "проблемой хрупкого базового класса".

Очевидное решение состоит в том, чтобы опустить "информацию, полезную для разработчиков" для классов, которые используются в качестве интерфейсов для пользователей. То есть создавать интерфейсы, чистые интерфейсы. То есть представлять интерфейсы в виде абстрактных классов:

	class Shape {
	public:		// interface to users of Shapes
		virtual void draw() const = 0;
		virtual void rotate(int degrees) = 0;
		virtual Point center() const = 0;
		// ...

		// no data
	};

	class Circle : public Shape {
	public:	
		void draw() const;
		void rotate(int) { }
		Point center() const { return cent; }
		// ...
	protected:
		Point cent;
		Color col;
		int radius;
		// ...
	};

	class Triangle : public Shape {
	public:	
		void draw() const;
		void rotate(int);
		Point center() const;
		// ...
	protected:
		Color col;
		Point a, b, c;
		// ...
	};	


Пользователи теперь защищены от изменений в реализациях производных классов. Я видел, как этот метод сокращал время сборки на порядки.
Но что, если действительно существует какая-то информация, общая для всех производных классов (или просто для нескольких производных классов)? Просто сделайте эту информацию классом и также извлеките из нее классы реализации:

	class Shape {
	public:		// interface to users of Shapes
		virtual void draw() const = 0;
		virtual void rotate(int degrees) = 0;
		virtual Point center() const = 0;
		// ...

		// no data
	};

	struct Common {
		Color col;
		// ...
	};
		
	class Circle : public Shape, protected Common {
	public:	
		void draw() const;
		void rotate(int) { }
		Point center() const { return cent; }
		// ...
	protected:
		Point cent;
		int radius;
	};

	class Triangle : public Shape, protected Common {
	public:	
		void draw() const;
		void rotate(int);
		Point center() const;
		// ...
	protected:
		Point a, b, c;
	};	

Почему размер пустого класса не равен нулю?

Чтобы гарантировать, что адреса двух разных объектов будут разными. По той же причине "new" всегда возвращает указатели на отдельные объекты. Пример:

	class Empty { };

	void f()
	{
		Empty a, b;
		if (&a == &b) cout << "impossible: report error to compiler supplier";

		Empty* p1 = new Empty;
		Empty* p2 = new Empty;
		if (p1 == p2) cout << "impossible: report error to compiler supplier";
	}	


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

	struct X : Empty {
		int a;
		// ...
	};

	void f(X* p)
	{
		void* p1 = p;
		void* p2 = &p->a;
		if (p1 == p2) cout << "nice: good optimizer";
	}


Эта оптимизация безопасна и может быть наиболее полезной. Это позволяет программисту использовать пустые классы для представления очень простых концепций без накладных расходов. Некоторые современные компиляторы обеспечивают такую "оптимизацию пустого базового класса".

Почему я должен помещать данные в свои объявления классов?

Вы не должны. Если вам не нужны данные в интерфейсе, не помещайте их в класс, который определяет интерфейс. Вместо этого поместите его в производные классы. См., Почему мои компиляции занимают так много времени?.
Иногда вы действительно хотите иметь данные представления в классе. Рассмотрим класс complex:

	template class complex {
	public:
		complex() : re(0), im(0) { }
		complex(Scalar r) : re(r), im(0) { }
		complex(Scalar r, Scalar i) : re(r), im(i) { }
		// ...

		complex& operator+=(const complex& a)
			{ re+=a.re; im+=a.im; return *this; }
		// ...
	private:
		Scalar re, im;
	};


Этот тип предназначен для использования в основном как встроенный тип, и представление необходимо в объявлении, чтобы сделать возможным создание действительно локальных объектов (например объектов, которые размещаются в стеке, а не в куче) и обеспечить правильное встраивание простых операций. Подлинно локальные объекты и встраивание необходимы, чтобы получить производительность комплекса, близкую к тому, что обеспечивается в языках со встроенным сложным типом.

Почему функции классов по умолчанию не являются виртуальными?

Потому что многие классы не предназначены для использования в качестве базовых классов. Например, см. класс complex.
Кроме того, объекты класса с виртуальной функцией требуют места, необходимого механизму вызова виртуальной функции - обычно по одному слову на объект. Эти накладные расходы могут быть значительными и могут помешать совместимости разметки с данными из других языков (например, C и Fortran).
Более подробное обоснование дизайна см. Дизайн и Эволюция C++.

Почему деструкторы по умолчанию не являются виртуальными?

Потому что многие классы не предназначены для использования в качестве базовых классов. Виртуальные функции имеют смысл только в классах, предназначенных для работы в качестве интерфейсов к объектам производных классов (обычно размещается в куче и доступен через указатели или ссылки).
Итак, когда я должен объявить деструктор виртуальным? Каждый раз, когда в классе есть хотя бы одна виртуальная функция. Наличие виртуальных функций указывает на то, что класс предназначен для использования в качестве интерфейса для производных классов, и когда это так, объект производного класса может быть уничтожен через указатель на базу. Например:

	class Base {
		// ...
		virtual ~Base();
	};

	class Derived : public Base {
		// ...
		~Derived();
	};

	void f()
	{
		Base* p = new Derived;
		delete p;	// virtual destructor used to ensure that ~Derived is called
	}


Если бы деструктор Базы не был виртуальным, Производный деструктор не был бы вызван - с вероятными плохими последствиями, такими как ресурсы, принадлежащие производному, которые не освобождаются.

Почему у нас нет виртуальных конструкторов?

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

	struct F {	// interface to object creation functions
		virtual A* make_an_A() const = 0;
		virtual B* make_a_B() const = 0;
	};

	void user(const F& fac)
	{
		A* p = fac.make_an_A();	// make an A of the appropriate type
		B* q = fac.make_a_B();	// make a B of the appropriate type
		// ...
	}

	struct FX : F {
		A* make_an_A() const { return new AX();	} // AX is derived from A
		B* make_a_B() const { return new BX();	} // BX is derived from B
	};

	struct FY : F {
		A* make_an_A() const { return new AY();	} // AY is derived from A
		B* make_a_B() const { return new BY();	} // BY is derived from B
	};

	int main()
	{
		FX x;
		FY y;
		user(x);	// this user makes AXs and BXs
		user(y);	// this user makes AYs and BYs

		user(FX());	// this user makes AXs and BXs
		user(FY());	// this user makes AYs and BYs
		// ...
	}


Это называются " заводской шаблон". Дело в том, что user() полностью изолирован от знаний о таких классах, как AX и AY.

Что такое простая виртуальная функция?

Простая виртуальная функция - это функция, которая должна быть переопределена в производном классе и не должна быть определена. Виртуальная функция объявляется "простой" с использованием любопытного синтаксиса "=0". Например:

	class Base {
	public:
		void f1();		// not virtual
		virtual void f2();	// virtual, not pure
		virtual void f3() = 0;	// pure virtual
	};

	Base b;	// error: pure virtual f3 not overridden


Здесь Base является абстрактным классом (поскольку он имеет простую виртуальную функцию), поэтому никакие объекты класса Base не могут быть созданы напрямую: Base (явно) предназначен для базового класса. Например:

	class Derived : public Base {
		// no f1: fine
		// no f2: fine, we inherit Base::f2
		void f3();
	};

	Derived d;	// ok: Derived::f3 overrides Base::f3


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

	Base::f3() { /* ... */ }


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

	class D2 : public Base {
		// no f1: fine
		// no f2: fine, we inherit Base::f2
		// no f3: fine, but D2 is therefore still abstract
	};

	D2 d;	// error: pure virtual Base::f3 not overridden

Почему перегрузка не работает для производных классов?

Этот вопрос (во многих вариантах) обычно задается таким примером, как этот:

	#include
	using namespace std;

	class B {
	public:
		int f(int i) { cout << "f(int): "; return i+1; }
		// ...
	};

	class D : public B {
	public:
		double f(double d) { cout << "f(double): "; return d+1.3; }
		// ...
	};

	int main()
	{
		D* pd = new D;

		cout << pd->f(2) << '\n';
		cout << pd->f(2.3) << '\n';
	}


который будет производить:

	f(double): 3.3
	f(double): 3.6


вместо того

	f(int): 3
	f(double): 3.6


как некоторые люди (ошибочно) догадались.
Другими словами, между D и B. нет резолюции перегрузки. Компилятор заглядывает в область D, находит единственную функцию "double f(double)" и вызывает ее. Это никогда не беспокоит (охватывающую) область B. В C++ нет перегрузки между областями видимости - области производных классов не являются исключением из этого общего правила. (Подробности см. D&E или TC++PL3).

Но что, если я хочу создать перегрузочный набор всех моих функций f () из моего базового и производного класса? Это легко сделать с помощью using-declaration:

	class D : public B {
	public:
		using B::f;	// make every f from B available
		double f(double d) { cout << "f(double): "; return d+1.3; }
		// ...
	};


Задав эту модификацию, результат будет

	f(int): 3
	f(double): 3.6


То есть разрешение перегрузки было применено к f() из B и f() из D, чтобы выбрать наиболее подходящий f() для вызова.

Могу ли я использовать "new" так же, как в Java?

Вроде можно, но не делайте этого вслепую, и часто есть лучшие альтернативы. Пример:

void compute(cmplx z, double d)
{
	cmplx z2 = z+d;	// c++ style
	z2 = f(z2);		// use z2

	cmplx& z3 = *new cmplx(z+d);	// Java style (assuming Java could overload +)
	z3 = f(z3);
	delete &z3;	
}


Неуклюжее использование "new" для z3 является ненужным и медленным по сравнению с идиоматическим использованием локальной переменной (z2). Вам не нужно использовать "new" для создания объекта, если вы также удаляете этот объект в той же области с помощью "delete"; такой объект должен быть локальной переменной.

Могу ли я вызвать виртуальную функцию из конструктора?

Да, но будьте осторожены. Это может не сработать так, как вы ожидаете. В конструкторе механизм виртуального вызова отключен, поскольку переопределение из производных классов еще не произошло. Объекты строятся от основания вверх, " base перед производным".
Пример:

	#include
	#include
	using namespace std;

	class B {
	public:
		B(const string& ss) { cout << "B constructor\n"; f(ss); }
		virtual void f(const string&) { cout << "B::f\n";}
	};

	class D : public B {
	public:
		D(const string & ss) :B(ss) { cout << "D constructor\n";}
		void f(const string& ss) { cout << "D::f\n"; s = ss; }
	private:
		string s;
	};

	int main()
	{
		D d("Hello");
	}


программа компилирует и производит

	B constructor
	B::f
	D constructor


Примечание: not D::f. Подумайте, что произошло бы, если бы правило было другим, чтобы D::f() вызывался из B::B(): Поскольку конструктор D::D() еще не был запущен, D::f() попытался бы присвоить свой аргумент неинициализированной строковый тип s. Результат, скорее всего, будет немедленный сбой.
Деструкция выполняется "производным классом перед базовым классом", поэтому виртуальные функции ведут себя как в конструкторах: используются только локальные определения - и не выполняются вызовы переопределяющих функций, чтобы избежать прикосновения к (теперь уничтоженной) части производного класса объекта.

Больше подробностей см. D&E 13.2.4.2 или TC++PL3 15.4.3.

Было высказано, что это правило является артефактом реализации. Это не так. На самом деле, было бы заметно проще реализовать небезопасное правило вызова виртуальных функций из конструкторов точно так же, как из других функций. Однако это означало бы, что никакая виртуальная функция не может быть написана так, чтобы полагаться на инварианты, установленные базовыми классами. Это был бы ужасный беспорядок.

Есть ли "удаление размещения"?

Нет, но если вам это нужно, вы можете написать свой собственный.
Рассмотрим размещение нового, используемого для размещения объектов в наборе арен.

        class Arena {
        public:
                void* allocate(size_t);
                void deallocate(void*);
                // ...
        };

        void* operator new(size_t sz, Arena& a)
        {
                return a.allocate(sz);
        }

        Arena a1(some arguments);
        Arena a2(some arguments);


Учитывая это, мы можем написать

        X* p1 = new(a1) X;
        Y* p2 = new(a1) Y;
        Z* p3 = new(a2) Z;
        // ...


Но как мы можем позже правильно удалить эти объекты? Причина, по которой нет встроенного "удаления размещения" для сопоставления с новым размещением, заключается в том, что нет общего способа гарантировать, что оно будет использоваться правильно. Ничто в системе типов C++ не позволяет нам сделать вывод, что p1 указывает на объект, выделенный на Арене a1. Указатель на любой X, выделенный в любом месте, может быть присвоен к p1.
Однако иногда программист действительно знает, и есть способ:

        template void destroy(T* p, Arena& a)
        {
                if (p) {
                        p->~T();		// explicit destructor call
                        a.deallocate(p);
                }
        }


Теперь мы можем написать:

        destroy(p1,a1);
        destroy(p2,a2);
        destroy(p3,a3);


Если Арена отслеживает, какие объекты она содержит, вы даже можете написать destroy(), чтобы защититься от ошибок.
Также возможно определить соответствующие пары оператор new() и оператор delete() для иерархии классов TC++PL(SE) 15.6. См. также D&E 10.4 and TC++PL(SE) 19.4.5.

Могу ли я остановить людей, происходящих из моего класса?

Да, но почему ты этого хочешь? Есть два распространенных ответа:

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

	struct Base {
		virtual void f();
	};

	struct Derived final : Base {	// now Derived is final; you cannot derive from it
		void f() override;
	};

	struct DD: Derived {// error: Derived is final

		// ...
	};


Для старых компиляторов вы можете использовать несколько неуклюжую технику:

	class Usable;

	class Usable_lock {
		friend class Usable;
	private:
		Usable_lock() {}
		Usable_lock(const Usable_lock&) {}
	};

	class Usable : public virtual Usable_lock {
		// ...
	public:
		Usable();
		Usable(char*);
		// ...
	};

	Usable a;

	class DD : public Usable { };

	DD dd;  // error: DD::DD() cannot access
        	// Usable_lock::Usable_lock(): private  member

Почему C++ не предоставляет гетерогенные контейнеры?

Стандартная библиотека C++ предоставляет набор полезных, статически типобезопасных и эффективных контейнеров. Примерами являются вектор, список и карта:

	vector vi(10);
	vector vs;
	list lst;
	list l2
	map tbl;
	map< Key,vector > t2;


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

	vector vi;	// vector of pointers to Shapes


Здесь vi может содержать элементы любого типа, производные от Shape. То есть vi однороден в том смысле, что все его элементы являются Shapes (точнее, указателями на Фигуры) и неоднороден в том смысле, что vi может содержать элементы самых разнообразных фигур, таких как Круги, Треугольники и т.д.
Таким образом, в некотором смысле все контейнеры (на любом языке) однородны, потому что для их использования должен быть общий интерфейс для всех элементов, на который пользователи могли бы положиться. Языки, которые предоставляют контейнеры, считающиеся гетерогенными, просто предоставляют контейнеры элементов, которые все предоставляют стандартный интерфейс. Например, коллекции Java предоставляют контейнеры (ссылки на) Objects, и вы используете (общий) Objects интерфейс для определения реального типа элемента.

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

Если вам нужен гетерогенный контейнер на C++, определите общий интерфейс для всех элементов и создайте из них контейнер. Например:

	class Io_obj { /* ... */ };	// the interface needed to take part in object I/O

	vector vio;		// if you want to manage the pointers directly
	vector< Handle > v2;	// if you want a "smart pointer" to handle the objects


Не опускайтесь до самого низкого уровня детализации реализации, если только в этом нет необходимости:

	vector memory;	// rarely needed


Хорошим признаком того, что вы "перешли на слишком низкий уровень", является то, что ваш код усеян приведениями.
Использование любого класса, такого как Boost::Any, может быть альтернативой в некоторых программах:

	vector v;

Почему стандартные контейнеры работают так медленно?

Это не так. Вероятно, "по сравнению с чем?" - более полезный ответ. Когда люди жалуются на производительность контейнера стандартной библиотеки, я обычно нахожу одну из трех реальных проблем (или один из многих мифов и отвлекающих маневров):

Прежде чем пытаться оптимизировать, подумайте, есть ли у вас реальная проблема с производительностью. В большинстве присланных мне случаев проблема с производительностью носит теоретический или воображаемый характер: сначала измеряйте, а затем оптимизируйте только в случае необходимости.
Давайте рассмотрим эти проблемы по очереди. Часто vector работает медленнее, чем чей-либо специализированный My_container, потому что My_container реализован как контейнер указателей на X. Стандартные контейнеры содержат копии значений и копируют значение, когда вы помещаете его в контейнер. Это, по сути, непобедимо для небольших значений, но может быть совершенно непригодно для огромных объектов:

	vector vi;
	vector vim;
	// ...
	int i = 7;
	Image im("portrait.jpg");	// initialize image from file
	// ...
	vi.push_back(i);	// put (a copy of) i into vi
	vim.push_back(im);	// put (a copy of) im into vim


Теперь, если portrait.jpg пара мегабайт, и изображение имеет значение семантики (т.е. Назначение копирования и создание копии создают копии), тогда vim.push_back(im) действительно будет дорогим процессом. Но как говорится - если это так больно, просто не делай этого. Вместо этого используйте либо контейнер дескрипторов, либо контейнеры указателей. Например, если изображение имело ссылочную семантику, приведенный выше код потребовал бы только затрат на вызов конструктора копирования, что было бы тривиально по сравнению с большинством операторов обработки изображений. Если какой-то класс, скажем, Image, по уважительным причинам имеет семантику копирования, контейнер указателей часто является разумным решением:

	vector vi;
	vector vim;
	// ...
	Image im("portrait.jpg");	// initialize image from file
	// ...
	vi.push_back(7);	// put (a copy of) 7 into vi
	vim.push_back(&im);	// put (a copy of) &im into vim


Естественно, если вы используете указатели, вам приходится думать об управлении ресурсами, но контейнеры указателей сами по себе могут быть эффективными и дешевыми дескрипторами ресурсов (часто вам нужен контейнер с деструктором для удаления "принадлежащих" объектов).
Вторая часто возникающая проблема с производительностью - это использование map для большого количества (string,X) пар. Карты хороши для относительно небольших контейнеров (скажем, несколько сотен или несколько тысяч элементов - доступ к элементу карты из 10000 элементов стоит около 9 сравнений), где менее дешевле, и где не может быть создана хорошая хэш-функция. Если у вас много строк и хорошая хэш-функция, используйте хэш таблицу. unordered_map из Технического Отчета комитета по стандартизации теперь широко доступна и намного лучше, чем у большинства людей.

Иногда вы можете ускорить процесс, используя (const char*,X) пары вместо (string,X) пар, но помните, что < не выполняет лексикографическое сравнение для строк в стиле C. Кроме того, если X большой, у вас также может возникнуть проблема с копированием (решите ее одним из обычных способов).

Навязчивые списки могут быть очень быстрыми. Однако подумайте, нужен ли вам список вообще: вектор более компактен и, следовательно, во многих случаях меньше и быстрее - даже когда вы выполняете вставки и стирания. Например, если у вас есть логически список из нескольких целых элементов, вектор значительно быстрее, чем список (любой список). Кроме того, навязчивые списки не могут содержать встроенные типы напрямую (int не имеет link элемента). Итак, предположим, что вам действительно нужен список и что вы можете указать поле ссылки для каждого типа элемента. Список стандартных библиотек по умолчанию выполняет выделение, за которым следует копирование для каждой операции вставки элемента (и освобождение для каждой операции элемента удаления). Для std::list с распределителем по умолчанию может быть значительным. Для небольших элементов, где накладные расходы на копирование незначительны, рассмотрите возможность использования оптимизированного распределителя. Используйте созданные вручную навязчивые списки только там, где требуется список и последняя унция производительности.

Люди иногда беспокоятся о том, что стоимость std::vector постепенно растет. Раньше я беспокоился об этом и использовал reserve() для оптимизации роста. После измерения моего кода и неоднократных проблем с поиском преимуществ reserve() в производительности в реальных программах я перестал использовать его, за исключением случаев, когда это необходимо, чтобы избежать недействительности итератора (редкий случай в моем коде). Еще раз: измеряйте, прежде чем оптимизировать.

Нарушает ли "friend" инкапсуляцию?

Нет. Это не так. " Friend" - это явный механизм предоставления доступа, как и членство. Вы не можете (в стандартной соответствующей программе) предоставить себе доступ к классу, не изменяя его исходный код. Например:

	class X {
		int i;
	public:
		void m();		// grant X::m() access
		friend void f(X&);	// grant f(X&) access
		// ...
	};

	void X::m() { i++; /* X::m() can access X::i */ }

	void f(X& x) { x.i++; /* f(X&) can access X::i */ }

Описание модели защиты C++ см. D&E раздел 2.10 и TC++PL разделы 11.5, 15.3, и C.11.

Почему мой конструктор не работает правильно?

Это вопрос, который возникает во многих формах. Такие как:

По умолчанию классу присваивается конструктор копирования и назначение копирования, которые копируют все элементы. Например:

	struct Point {
		int x,y;
		Point(int xx = 0, int yy = 0) :x(xx), y(yy) { }
	};

	Point p1(1,2);
	Point p2 = p1;	

Здесь мы получаем p2.x==p1.x и p2.y==p1.y. Часто это именно то, что вы хотите (и важно для совместимости с C), но учтите:

	class Handle {
	private:
		string name;
		X* p;
	public:
		Handle(string n)
			:name(n), p(0) { /* acquire X called "name" and let p point to it */ }
		~Handle() { delete p; /* release X called "name" */ }
		// ...
	};

	void f(const string& hh)
	{
		Handle h1(hh);
		Handle h2 = h1;	// leads to disaster!
		// ...
	}

Здесь копия по умолчанию дает нам h2.name==h1.name и h2.p==h1.p. Это приводит к катастрофе: когда мы выходим из f(), вызываются деструкторы для h1 и h2, и объект, на который указывают h1.p и h2.p, удаляется дважды.
Как нам избежать этого? Самое простое решение - предотвратить копирование, сделав операции копирования private:

	class Handle {
	private:
		string name;
		X* p;

		Handle(const Handle&);	// prevent copying
		Handle& operator=(const Handle&);
	public:
		Handle(string n)
			:name(n), p(0) { /* acquire the X called "name" and let p point to it */ }
		~Handle() { delete p; /* release X called "name" */ }
		// ...
	};

	void f(const string& hh)
	{
		Handle h1(hh);
		Handle h2 = h1;	// error (reported by compiler)
		// ...
	}

Если нам нужно скопировать, мы, конечно, можем определить инициализатор копирования и назначение копирования, чтобы обеспечить желаемую семантику.
Теперь вернемся к сути. Для Point семантика копирования по умолчанию в порядке, проблема заключается в конструкторе:

	struct Point {
		int x,y;
		Point(int xx = 0, int yy = 0) :x(xx), y(yy) { }
	};

	void f(Point);

	void g()
	{
		Point orig;	// create orig with the default value (0,0)
		Point p1(2);	// create p1 with the default y-coordinate 0
		f(2);		// calls Point(2,0);
	}

Люди предоставляют аргументы по умолчанию, чтобы получить удобство, используемое для orig и p1. Затем некоторые удивляются конвертированию 2 в Point(2,0) в призыве off(). Конструктор, принимающий один аргумент, определяет преобразование. По умолчанию это неявное конвертирование. Чтобы потребовать, чтобы такое конвертирование было отделным, объявите конструктор отделно:

	struct Point {
		int x,y;
		explicit Point(int xx = 0, int yy = 0) :x(xx), y(yy) { }
	};

	void f(Point);

	void g()
	{
		Point orig;	// create orig with the default value (0,0)
		Point p1(2);	// create p1 with the default y-coordinate 0
				// that's an explicit call of the constructor
		f(2);		// error (attmpted implicit conversion)
		Point p2 = 2;	// error (attmpted implicit conversion)
		Point p3 = Point(2);	// ok (explicit conversion)
	}

Почему в C++ есть как указатели, так и ссылки?

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

	void f1(const complex* x, const complex* y)	// without references
	{
		complex z = *x+*y;	// ugly
		// ...
	}

	void f2(const complex& x, const complex& y)	// with references
	{
		complex z = x+y;	// better
		// ...
	}	

В более общем плане, если вы хотите иметь как функциональность указателей, так и функциональность ссылок, вам нужны либо два разных типа (как в C++), либо два разных набора операций над одним типом. Например, для одного типа вам нужна как операция для присвоения объекту, на который ссылается ссылка, так и операция для присвоения ссылке /указателю. Это можно сделать с помощью отдельных операторов (как в Simula). Например:

	Ref r :- new My_type;
	r := 7;			// assign to object
	r :- new My_type;	// assign to reference

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

	Ref r = new My_type;
	r = 7;			// assign to object
	r = new My_type;	// assign to reference


Мне лучше использовать вызов по значению или вызов по ссылке?

Это зависит от того, чего вы пытаетесь достичь:

Что я подразумеваю под "большим"? Что-нибудь большее, чем пара слов.
Почему я должен хотеть изменить аргумент? Что ж, часто нам приходится это делать, но часто у нас есть альтернатива: создать новую ценность. Например:

	void incr1(int& x);	// increment
	int incr2(int x);	// increment

	int v = 2;
	incr1(v);	// v becomes 3
	v = incr2(v);	// v becomes 4

Я думаю, что для читателя inc r2() легче понять. То есть incr1() с большей вероятностью приведет к ошибкам и ошибкам. Итак, я бы предпочел стиль, который возвращает новое значение, стилю, который изменяет значение, если создание и копирование нового значения не являются дорогостоящими.
Я действительно хочу изменить аргумент, должен ли я использовать указатель или я должен использовать ссылку? Я не знаю веской логической причины. Если передача ``not an object'' (например, нулевого указателя) допустима, использование указателя имеет смысл. Мой личный стиль заключается в использовании указателя, когда я хочу изменить объект, потому что в некоторых контекстах это облегчает определение возможности модификации.

Обратите также внимание, что вызов функции-члена по сути является вызовом по ссылке на объект, поэтому мы часто используем функции-члены, когда хотим изменить значение/состояние объекта.

Почему "this" не является ссылкой?

Потому что "this" было введено в C++ (на самом деле в C с классами) до того, как были добавлены ссылки. Кроме того, я выбрал "this", чтобы следовать Simula использованию, а не (более позднему) Smalltalk используя "self".

Что не так с массивами?

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

Две фундаментальные проблемы с массивами заключаются в том, что

Рассмотрим несколько примеров:

	void f(int a[], int s)
	{
		// do something with a; the size of a is s
		for (int i = 0; i

Второй вызов будет заполнять всю память, которая не принадлежит arr2. Естественно, программист обычно правильно определяет размер, но это дополнительная работа, и очень часто кто-то совершает ошибку. Я предпочитаю более простую и чистую версию, использующую стандартную библиотеку vector:

	void f(vector& v)
	{
		// do something with v
		for (int i = 0; i v1(20);
	vector v2(10);

	void g()
	{
		f(v1);
		f(v2);
	}

Поскольку массив не знает своего размера, назначение массива не может быть выполнено:

	void f(int a[], int b[], int size)
	{
		a = b;	// not array assignment
		memcpy(a,b,size);	// a = b
		// ...
	}

Опять же, я предпочитаю вектор:

	void g(vector& a, vector& b, int size)
	{
		a = b;	
		// ...
	}

Еще одним преимуществом vector здесь является то, что memcpy() не собирается делать правильные вещи для элементов с конструкторами копирования, такими как строки.

	void f(string a[], string b[], int size)
	{
		a = b;	// not array assignment
		memcpy(a,b,size);	// disaster
		// ...
	}

	void g(vector& a, vector& b, int size)
	{
		a = b;	
		// ...
	}

Массив имеет фиксированный размер, определяемый во время компиляции:

	const int S = 10;

	void f(int s)
	{
		int a1[s];	// error
		int a2[S];	// ok

		// if I want to extend a2, I'll have to change to an array
		// allocated on free store using malloc() and use realloc()
		// ...
	}

Для контраста:

	const int S = 10;

	void g(int s)
	{
		vector v1(s);	// ok
		vector v2(S);	// ok
		v2.resize(v2.size()*2);
		// ...
	}

C99 допускает границы переменных массивов для локальных массивов, но у этих Vlan есть свои собственные проблемы.
То, как имена массивов " decay" на указатели, имеет основополагающее значение для их использования в C и C++. Однако распад массива очень плохо взаимодействует с наследованием. Рассмотрим:

	class Base { void fct(); /* ... */ };
	class Derived : Base { /* ... */ };

	void f(Base* p, int sz)
	{
		for (int i=0; i<sz; ++i) p[i].fct();
	}

	Base ab[20];
	Derived ad[20];

	void g()
	{
		f(ab,20);
		f(ad,20);	// disaster!
	}

В последнем вызове Derived[] обрабатывается как Base[], и подписка больше не работает корректно, когда sizeof(Derived)!=sizeof(Base) -- как это будет иметь место в большинстве интересующих случаев. Если бы вместо этого мы использовали векторы, ошибка была бы обнаружена во время компиляции:

	void f(vector<Base>& v)
	{
		for (int i=0; i<v.size(); ++i) v[i].fct();
	}

	vector<Base> ab(20);
	vector<Derived> ad(20);

	void g()
	{
		f(ab);
		f(ad);	// error: cannot convert a vector<Derived> to a vector<Base>
	}

Я нахожу, что поразительное количество ошибок начинающих программистов в C и C++ связано с (неправильным) использованием массивов.

Почему в C++ нет окончательного ключевого слова?

Есть, но это не так полезно, как вы могли бы подумать.

Мне лучше использовать NULL или 0?

В C++ определение NULL равно 0, так что разница только эстетическая. Я предпочитаю избегать макросов, поэтому я использую 0. Другая проблема с NULL заключается в том, что люди иногда ошибочно полагают, что оно отличается от 0 и/или не является целым числом. В стандартном коде значение NULL иногда определялось как нечто неподходящее, и поэтому его следовало/следует избегать. В наши дни это встречается реже.
Если вам нужно присвоить нулевому указателю, назовите его nullptr; именно так он называется в C++11. Тогда ключевым словом будет "nullptr".

Как объекты С++ размещаются в памяти?

Как и C, C++ не определяет макеты, только семантические ограничения, которые должны быть выполнены. Поэтому разные реализации делают что-то по-разному. К сожалению, лучшее объяснение, которое я знаю, содержится в книге, которая в остальном устарела и не описывает какую-либо текущую реализацию C++: Аннотированное справочное руководство по C++ (обычно называемое ARM). В нем есть диаграммы примеров компоновки ключей. Существует очень краткое объяснение в главе 2 в TC++PL.
По сути, C++ создает объекты просто путем объединения вложенных объектов. Таким образом,

        struct A { int a,b; };

представлен двумя целыми числами рядом друг с другом и

        struct B : A { int c; };

представлен буквой A, за которой следует int; то есть тремя целыми числами рядом друг с другом.

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

Каково значение i++ + i++?

Это не определено. В принципе, в C и C++, если вы дважды прочитаете переменную в выражении, в котором вы ее также записываете, результат не определен. Не делай этого. Другим примером является:

	v[i] = i++;

Связанный пример:

	f(v[i],i++);

Здесь результат не определен, поскольку порядок вычисления аргументов функции не определен.
Утверждается, что неопределенный порядок вычисления обеспечивает более высокую производительность кода. Компиляторы могли бы предупреждать о таких примерах, которые обычно являются незначительными ошибками (или потенциальными незначительными ошибками). Я разочарован тем, что по прошествии десятилетий большинство компиляторов все еще не предупреждают, оставляя эту работу специализированным, отдельным и недостаточно используемым инструментам.

Почему некоторые вещи остаются неопределенными в C++?

Потому что машины различаются и потому, что C оставил многие вещи неопределенными. Для получения подробной информации, включая определения терминов " undefined", " unspecified", " implementation defined" и " well-formed"; см. стандарт ISO C++. Обратите внимание, что значение этих терминов отличается от их определения в стандарте ISO C и от некоторых распространенных терминов. Вы можете получить удивительно запутанные дискуссии, когда люди не понимают, что не все разделяют определения.

Это правильный, хотя и неудовлетворительный ответ. Как и C, C++ предназначен для прямого и эффективного использования аппаратного обеспечения. Это подразумевает, что C++ должен иметь дело с аппаратными объектами, такими как биты, байты, слова, адреса, целочисленные вычисления и вычисления с плавающей запятой, такими, какими они являются на данной машине, а не такими, какими мы хотели бы их видеть. Обратите внимание, что многие "вещи", которые люди называют "неопределенными", на самом деле являются "определенными реализацией", так что мы можем писать идеально определенный код, если знаем, на какой машине мы работаем. Размеры целых чисел и поведение округления вычислений с плавающей запятой относятся к этой категории.

Рассмотрим, что, вероятно, является самым известным и самым печально известным примером неопределенного поведения:

	int a[10];
	a[100] = 0;	// range error
	int* p = a;
	// ...
	p[100] = 0;	// range error (unless we gave p a better value before that assignment)

Понятия массива и указателя в C++ (и C) являются прямыми представлениями машинного представления о памяти и адресах, предоставляемыми без каких-либо накладных расходов. Примитивные операции с указателями отображаются непосредственно на машинные инструкции. В частности, проверка диапазона не выполняется. Выполнение проверки диапазона повлечет за собой затраты с точки зрения времени выполнения и размера кода. C был разработан для того, чтобы вытеснять ассемблерный код для задач операционных систем, так что это было необходимое решение. Кроме того, C - в отличие от C++ - не имеет разумного способа сообщить о нарушении, если компилятор решил сгенерировать код для его обнаружения: В C нет исключений. C++ последовал за C по соображениям совместимости и потому, что C++ также напрямую конкурирует с ассемблером (в ОС, встроенных системах и некоторых областях числовых вычислений). Если вы хотите проверить диапазон, используйте подходящий проверяемый класс (вектор, интеллектуальный указатель, строка и т.д.). Хороший компилятор мог бы перехватить ошибку диапазона для a[100] во время компиляции, перехватить ошибку для p[100] гораздо сложнее, и, как правило, невозможно перехватить каждую ошибку диапазона во время компиляции.
Другие примеры неопределенного поведения вытекают из модели компиляции. Компилятор не может обнаружить несогласованное определение объекта или функции в отдельно скомпилированных единицах перевода. Например:

	// file1.c:
	struct S { int x,y; };
	int f(struct S* p) { return p->x; }

	// file2.c:
	struct S { int y,x; }
	int main()
	{
		struct S s;
		s.x = 1;
		int x = f(&s);	// x!=s.x !!
		return 2;
	}

Компиляция file1.c и file2.c и объединение результатов в одну и ту же программу являются незаконными как в C, так и в C++. Компоновщик может перехватить непоследовательное определение S, но не обязан этого делать (и большинство этого не делает). Во многих случаях бывает довольно сложно обнаружить несоответствия между отдельно составленными единицами перевода. Последовательное использование файлов заголовков помогает свести к минимуму такие проблемы, и есть некоторые признаки того, что компоновщики улучшаются. Обратите внимание, что компоновщики C++ улавливают почти все ошибки, связанные с непоследовательно объявленными функциями.
Наконец, у нас есть явно ненужное и довольно раздражающее неопределенное поведение отдельных выражений. Например:

	void out1() { cout << 1; }
	void out2() { cout << 2; }

	int main()
	{
		int i = 10;
		int j = ++i + i++;	// value of j unspecified
		f(out1(),out2());	// prints 12 or 21
	}

Значение j не указано, чтобы компиляторы могли создавать оптимальный код. Утверждается, что разница между тем, что может быть создано, предоставляя компилятору такую свободу, и требуя "обычной оценки слева направо", может быть значительной. Я не убежден, но с учетом того, что бесчисленные компиляторы "там" пользуются преимуществами свободы, а некоторые люди страстно защищают эту свободу, изменение будет трудным и может занять десятилетия, чтобы проникнуть в отдаленные уголки миров C и C ++. Я разочарован тем, что не все компиляторы предупреждают о таком коде, как ++i+i++. Аналогично, порядок вычисления аргументов не определен.
IMO слишком много "вещей" остаются неопределенными, неуказанными, определенными реализацией и т.д. Однако это легко сказать и даже привести примеры, но трудно исправить. Следует также отметить, что избежать большинства проблем и создать переносимый код не так уж сложно.

Почему я не могу определить ограничения для параметров моего шаблона?

Что ж, вы можете, и это довольно просто и общее.
Рассмотрим:

        template
        void draw_all(Container& c)
        {
                for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
        }

Если есть тип error, он будет в разрешении довольно сложного вызова for_each(). Например, если тип элемента контейнера - int, то мы получаем какую-то неясную ошибку, связанную с вызовом for_each() (потому что мы не можем вызвать Shape::draw() для int).
Чтобы выявить такие ошибки на ранней стадии, я могу написать:

        template
        void draw_all(Container& c)
        {
                Shape* p = c.front(); // accept only containers of Shape*s

                for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
        }

Инициализация ложной переменной "p" вызовет понятное сообщение об ошибке от большинства современных компиляторов. Подобные трюки распространены во всех языках и должны быть разработаны для всех новых конструкций. В производственном коде я бы, наверное, написал что-то вроде:

	template
        void draw_all(Container& c)
        {
                typedef typename Container::value_type T;
                Can_copy(); // accept containers of only Shape*s

                for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
        }

Это ясно дает понять, что я делаю утверждение. Шаблон Can_copy может быть определен следующим образом:

	template struct Can_copy {
		static void constraints(T1 a, T2 b) { T2 c = a; b = a; }
		Can_copy() { void(*p)(T1,T2) = constraints; }
	};

Can_copy проверяет (во время компиляции), что T1 может быть присвоен T2. Can_copy проверяет, является ли T - Shape* или указателем на класс, публично производный от Shape, или типом с определяемым пользователем преобразованием в Shape*. Обратите внимание, что определение близко к минимальному:

Обратите также внимание, что определение обладает желательными свойствами:

Так почему же чего-то вроде Can_copy() - или чего-то еще более элегантного - нет в языке? D&E содержит анализ трудностей, связанных с выражением общих ограничений для C++. С тех пор появилось много идей для упрощения написания этих классов ограничений, которые по-прежнему вызывают хорошие сообщения об ошибках. Например, я считаю, что использование указателя для работы так, как я это делаю в Can_copy, происходит от Алекса Степанова и Джереми Сика. Я не думаю, что Can_copy() вполне готов к стандартизации - он нуждается в большем использовании. Кроме того, в сообществе C++ используются различные формы ограничений; до сих пор нет единого мнения о том, какая именно форма шаблонов ограничений является наиболее эффективной в широком диапазоне применений.

Однако идея очень общая, более общая, чем языковые средства, которые были предложены и предоставлены специально для проверки ограничений. В конце концов, когда мы пишем шаблон, нам доступна вся выразительная мощь C++. Рассмотрим:

	template<class T, class B> struct Derived_from {
		static void constraints(T* p) { B* pb = p; }
		Derived_from() { void(*p)(T*) = constraints; }
	};

	template<class T1, class T2> struct Can_copy {
		static void constraints(T1 a, T2 b) { T2 c = a; b = a; }
		Can_copy() { void(*p)(T1,T2) = constraints; }
	};

	template<class T1, class T2 = T1> struct Can_compare {
		static void constraints(T1 a, T2 b) { a==b; a!=b; a<b; }
		Can_compare() { void(*p)(T1,T2) = constraints; }
	};

	template<class T1, class T2, class T3 = T1> struct Can_multiply {
		static void constraints(T1 a, T2 b, T3 c) { c = a*b; }
		Can_multiply() { void(*p)(T1,T2,T3) = constraints; }
	};

	struct B { };
	struct D : B { };
	struct DD : D { };
	struct X { };

	int main()
	{
		Derived_from<D,B>();
		Derived_from<DD,B>();
		Derived_from<X,B>();
		Derived_from<int,B>();
		Derived_from<X,int>();

		Can_compare<int,float>();
		Can_compare<X,B>();
		Can_multiply<int,float>();
		Can_multiply<int,float,double>();
		Can_multiply<B,X>();
	
		Can_copy<D*,B*>();
		Can_copy<D,B*>();
		Can_copy<int,B*>();
	}

	// the classical "elements must derived from Mybase*" constraint:

	template<class T> class Container : Derived_from<T,Mybase> {
		// ...
	};

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

Зачем использовать sort(), когда у нас уже есть "хороший старый qsort()"?

Для новичка,

	qsort(array,asize,sizeof(elem),elem_compare);

выглядит довольно странно, и его труднее понять, чем

	sort(vec.begin(),vec.end());

Для эксперта тот факт, что sort(), как правило, быстрее, чем qsort() для одних и тех же элементов и одних и тех же критериев сравнения, часто имеет значение. Кроме того, sort() является универсальным, так что его можно использовать для любой разумной комбинации типа container, типа element и критерия сравнения. Например:

	struct Record {
		string name;
		// ...
	};

	struct name_compare {	// compare Records using "name" as the key
		bool operator()(const Record& a, const Record& b) const
			{ return a.name<b.name; }
	};

	void f(vector<Record>& vs)
	{
		sort(vs.begin(), vs.end(), name_compare());
		// ...
	}	

Кроме того, большинство людей ценят, что sort() является типобезопасным, что для его использования не требуется никаких приведений и что им не нужно писать функцию compare() для стандартных типов.

Для получения более подробного объяснения см. Мою статью "Изучение C++ как нового языка", которую вы можете скачать из моего списка публикаций.

Основная причина, по которой sort() имеет тенденцию превосходить qsort(), заключается в том, что сравнение в строках лучше.

Что такое функционал объект?

Объект, который в некотором роде ведет себя как функция, конечно. Как правило, это будет означать объект класса, который определяет оператор приложения - operator().
Функциональный объект - это более общее понятие, чем функция, поскольку функциональный объект может иметь состояние, которое сохраняется при нескольких вызовах (например, статическая локальная переменная) и может быть инициализирован и проверен извне объекта (в отличие от статической локальной переменной). Например:

	class Sum {
		int val;
	public:
		Sum(int i) :val(i) { }
		operator int() const { return val; }		// extract value

		int operator()(int i) { return val+=i; }	// application
	};

	void f(vector<int> v)
	{
		Sum s = 0;	// initial value 0
		s = for_each(v.begin(), v.end(), s);	// gather the sum of all elements
		cout << "the sum is " << s << "\n";
		
		// or even:
		cout << "the sum is " << for_each(v.begin(), v.end(), Sum(0)) << "\n";
	}

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

Как мне работать с утечками памяти?

Написав код, которого нет.
Очевидно, что если в вашем коде повсюду есть новые операции, операции удаления и арифметика указателей, вы где-то напортачите и получите утечки, случайные указатели и т.д. Это верно независимо от того, насколько добросовестно вы относитесь к своим распределениям: в конечном итоге сложность кода превысит время и усилия, которые вы можете себе позволить. Отсюда следует, что успешные методы основаны на скрытии распределения и освобождения внутри более управляемых типов. Хорошими примерами являются стандартные контейнеры. Они управляют памятью для своих элементов лучше, чем вы могли бы без непропорциональных усилий. Подумайте о том, чтобы написать это без помощи строки и вектора:

	#include<vector>
	#include<string>
	#include<iostream>
	#include<algorithm>
	using namespace std;

	int main()	// small program messing around with strings
	{
		cout << "enter some whitespace-separated words:\n";
		vector<string> v;
		string s;
		while (cin>>s) v.push_back(s);

		sort(v.begin(),v.end());

		string cat;
		typedef vector<string>::const_iterator Iter;
		for (Iter p = v.begin(); p!=v.end(); ++p) cat += *p+"+";
		cout << cat << '\n';
	}

Каков был бы ваш шанс сделать это правильно с первого раза? И откуда вам знать, что у вас не было утечки?
Обратите внимание на отсутствие явного управления памятью, макросов, приведений, проверок переполнения, явных ограничений размера и указателей. Используя объект функции и стандартный алгоритм, я мог бы отказаться от использования итератора, подобного указателю, но это казалось излишним для такой крошечной программы.

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

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

Шаблоны и стандартные библиотеки делают использование контейнеров, дескрипторов ресурсов и т.д. Намного проще, чем это было даже несколько лет назад. Использование исключений делает его близким к существенному.

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

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

	struct S {
		S() { cout << "make an S\n"; }
		~S() { cout << "destroy an S\n"; }
		S(const S&) { cout << "copy initialize an S\n"; }
		S& operator=(const S&) { cout << "copy assign an S\n"; }
	};

	S* f()
	{
		return new S;	// who is responsible for deleting this S?
	};

	auto_ptr<S> g()
	{
		return auto_ptr<S>(new S);	// explicitly transfer responsibility for deleting this S
	}

	int main()
	{
		cout << "start main\n";
		S* p = f();
		cout << "after f() before g()\n";
	//	S* q = g();	// this error would be caught by the compiler
		auto_ptr<S> q = g();
		cout << "exit main\n";
		// leaks *p
		// implicitly deletes *q
	}

Думайте о ресурсах в целом, а не просто о памяти.

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

Почему я не могу продолжать процесс после обнаружения исключения?

Другими словами, почему C++ не предоставляет примитив для возврата к точке, из которой было выдано исключение, и продолжения выполнения оттуда?
По сути, кто-то, возобновляющий работу с обработчиком исключений, никогда не может быть уверен, что код после точки броска был написан для того, чтобы выполнение продолжалось так, как будто ничего не произошло. Обработчик исключений не может знать, сколько контекста нужно "исправить" перед возобновлением. Чтобы правильно использовать такой код, автору броска и автору улова необходимо глубокое знание кода и контекста друг друга. Это создает сложную взаимную зависимость, которая везде, где это было разрешено, приводила к серьезным проблемам с обслуживанием.

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

Если вы хотите проверить, можете ли вы устранить проблему, прежде чем выдавать исключение, вызовите функцию, которая проверяет, а затем выдает только в том случае, если проблема не может быть решена локально. Примером этого является new_handler.

Почему в C++ нет эквивалента realloc()?

Если вы хотите, вы, конечно, можете использовать realloc(). Однако realloc() гарантированно работает только с массивами, выделяемыми malloc() (и аналогичными функциями), содержащими объекты без определяемых пользователем конструкторов копирования. Также, пожалуйста, помните, что вопреки наивным ожиданиям, realloc() иногда копирует свой массив аргументов.
В C++ лучший способ справиться с перераспределением - использовать стандартный библиотечный контейнер, такой как vector, и позволить ему расти естественным образом.

Зачем использовать исключения?

Что хорошего может принести мне использование исключений? Основной ответ таков: использование исключений для обработки ошибок делает ваш код проще, чище и с меньшей вероятностью пропускает ошибки. Но что плохого в "старых добрых errno и if-операторы"? Основной ответ таков: используя их, ваша обработка ошибок и ваш обычный код тесно переплетены. Таким образом, ваш код становится запутанным, и становится трудно убедиться, что вы разобрались со всеми ошибками (подумайте о "коде спагетти" или "крысином гнезде тестов").
Прежде всего, есть вещи, которые просто невозможно сделать правильно без исключений. Рассмотрим ошибку, обнаруженную в конструкторе; как вы сообщаете об ошибке? Вы создаете исключение. Это основа RAII (Инициализация приобретения ресурсов), которая лежит в основе некоторых из наиболее эффективных современных методов проектирования C++: задача конструктора - установить инвариант для класса (создать среду, в которой должны выполняться функции-члены), и это часто требует приобретения ресурсов, таких как память, блокировки, файлы, сокеты и т.д.

Представьте, что у нас не было исключений, как бы вы поступили с ошибкой, обнаруженной в конструкторе? Помните, что конструкторы часто вызываются для инициализации/построения объектов в переменных:

	vector<double> v(100000);   // needs to allocate memory
	ofstream os("myfile");      // needs to open a file

Конструктор vector или ofstream (поток выходного файла) может либо перевести переменную в "плохое" состояние (как это делает ifstream по умолчанию), чтобы каждая последующая операция завершалась ошибкой. Это не идеально. Например, в случае ofstream ваш вывод просто исчезает, если вы забудете проверить, что операция открытия прошла успешно. Для большинства классов результаты хуже. По крайней мере, мы должны были бы написать:

	vector<double> v(100000);   // needs to allocate memory
	if (v.bad()) { /* handle error */ }	// vector doesn't actually have a bad(); it relies on exceptions
	ofstream os("myfile");      // needs to open a file
	if (os.bad())  { /* handle error */ }

Это дополнительный тест для каждого объекта (для записи, запоминания или забвения). Это становится действительно запутанным для классов, состоящих из нескольких объектов, особенно если эти подобъекты зависят друг от друга. Для получения дополнительной информации см. Языка программирования C++, раздел 8.3, главу 14 и Приложение E или (более академическую) статью Безопасность исключений: Концепции и методы.
Таким образом, написание конструкторов может быть сложным без исключений, но как насчет простых старых функций? Мы можем либо вернуть код ошибки, либо установить нелокальную переменную (например, errno). Установка глобальной переменной работает не слишком хорошо, если вы не протестируете ее немедленно (или какая-либо другая функция могла сбросить ее). Даже не думайте об этом методе, если у вас может быть несколько потоков, обращающихся к глобальной переменной. Проблема с возвращаемыми значениями заключается в том, что выбор возвращаемого значения ошибки может потребовать сообразительности и может оказаться невозможным:

	double d = my_sqrt(-1);		// return -1 in case of error
	if (d == -1) { /* handle error */ }
	int x = my_negate(INT_MIN);	// Duh?

Для my_negate() невозможно вернуть значение: каждое возможное значение int является правильным ответом для некоторого значения int, и нет правильного ответа для самого отрицательного числа в представлении с двойным дополнением. В таких случаях нам нужно было бы возвращать пары значений (и, как обычно, не забывать проверять) Дополнительные примеры и объяснения см. в моей книге Начало программирования.
Общие возражения против использования исключений:

•    но исключения стоят дорого!: Не совсем. Современные реализации C++ снижают накладные расходы на использование исключений до нескольких процентов (скажем, 3%), и это по сравнению с отсутствием обработки ошибок. Написание кода с кодами возврата ошибок и тестами также не является бесплатным. Как правило, обработка исключений обходится чрезвычайно дешево, если вы не создаете исключение. В некоторых реализациях это ничего не стоит. Все затраты возникают, когда вы создаете исключение: то есть "обычный код" выполняется быстрее, чем код, использующий коды возврата ошибок и тесты. Вы несете расходы только в том случае, если у вас есть ошибка.
•    но в JSF++ вы сами прямо запрещаете исключения!: JSF++ предназначен для приложений, работающих в режиме реального времени и критически важных для безопасности (программное обеспечение для управления полетом). Если вычисление займет слишком много времени, кто-то может умереть. По этой причине мы должны гарантировать время отклика, и мы не можем - при текущем уровне поддержки инструментов - сделать это для исключений. В этом контексте даже бесплатное размещение магазина запрещено! На самом деле, рекомендации JSF++ по обработке ошибок имитируют использование исключений в ожидании того дня, когда у нас появятся инструменты, позволяющие все делать правильно, то есть использовать исключения.
•    но выбрасывание исключения из конструктора, вызываемого new, приводит к утечке памяти!: Ерунда! Это бабушкина сказка, вызванная ошибкой в одном компиляторе, и эта ошибка была немедленно исправлена более десяти лет назад.

Как мне использовать исключения?

См. Языка программирования C++, раздел 8.3, главу 14 и Приложение E. Приложение посвящено методам написания кода, защищенного от исключений, в требовательных приложениях и написано не для новичков.
В C++ исключения используются для сигнализации ошибок, которые не могут быть обработаны локально, таких как сбой при получении ресурса в конструкторе. Например:

	class Vector {
		int sz;
		int* elem;
		class Range_error { };
	public:
		Vector(int s) : sz(s) { if (sz<0) throw Range_error(); /* ... */ }
		// ...
	};

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

	void fct(string s)
	{
		File_handle f(s,"r");	// File_handle's constructor opens the file called "s"
		// use f
	} // here File_handle's destructor closes the file	

Если "use f" часть fct() выдает исключение, деструктор все равно вызывается, и файл закрывается должным образом. Это контрастирует с обычным небезопасным использованием:

	void old_fct(const char* s)
	{
		FILE* f = fopen(s,"r");	// open the file named "s"
		// use f
		fclose(f);	// close the file
	}

Если "use f" часть old_fct выдает исключение - или просто возвращает - файл не закрывается. В программах на языке С функция longjmp() представляет дополнительную опасность.

Почему я не могу назначить вектор<Яблоко> вектору<Фрукт>?

Потому что это открыло бы дыру в системе типов. Например:

	class Apple : public Fruit { void apple_fct(); /* ... */ };
	class Orange : public Fruit { /* ... */ }; // Orange doesn't have apple_fct()

	vector<Apple*> v;	// vector of Apples

	void f(vector<Fruit*>& vf)		// innocent Fruit manipulating function
	{
		vf.push_back(new Orange);	// add orange to vector of fruit
	}

	void h()
	{
		f(v);	// error: cannot pass a vector<Apple*> as a vector<Fruit*>
		for (int i=0; i<v.size(); ++i) v[i]->apple_fct();
	}

Если бы вызов f(v) был законным, у нас был бы Апельсин, притворяющийся Яблоком.
Альтернативным решением по разработке языка было бы разрешить небезопасную конвертацию, но полагаться на динамическую проверку. Это потребовало бы проверки во время выполнения для каждого доступа к членам v, и h() пришлось бы выдавать исключение при обнаружении последнего элемента v.

stopped


Почему в C++ нет универсального Object класса?

Да. Я упростил аргументы; это часто задаваемые вопросы, а не академическая статья.

Действительно ли нам нужно множественное наследование?

Не совсем. Мы можем обойтись без множественного наследования, используя обходные пути, точно так же, как мы можем обойтись без одиночного наследования, используя обходные пути. Мы даже можем обойтись без классов, используя обходные пути. C является доказательством этого утверждения. Однако каждый современный язык со статической проверкой типов и наследованием обеспечивает ту или иную форму множественного наследования. В C++ абстрактные классы часто служат интерфейсами, и класс может иметь множество интерфейсов. Другие языки, которые часто считаются "не MI", просто имеют отдельное название для своего эквивалента чистому абстрактному классу: интерфейс. Причина, по которой языки обеспечивают наследование (как одиночное, так и множественное), заключается в том, что наследование, поддерживаемое языком, обычно превосходит обходные пути (например, использование функций пересылки к подобъектам или отдельно выделенным объектам) для простоты программирования, для обнаружения логических проблем, для удобства обслуживания и часто для производительности.

Как мне читать строковый тип из ввода?

Вы можете прочитать одно слово, заканчивающееся пробелом, следующим образом:

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

	int main()
	{
		cout << "Please enter a word:\n";

		string s;
		cin>>s;
	
		cout << "You entered " << s << '\n';
	}

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

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

	int main()
	{
		cout << "Please enter a line:\n";

		string s;
		getline(cin,s);
	
		cout << "You entered " << s << '\n';
	}

Краткое введение в стандартные библиотечные средства, такие как iostream и строковый тип, см. TC++PL3 в главе 3 (доступно онлайн). Подробное сравнение простого использования ввода/вывода C и C++ см. в разделе "Изучение стандартного C++ как нового языка", который вы можете скачать из моего списка публикаций

Должны ли "общие" быть на шаблоны?

Нет. общие(дженерики)- это в первую очередь синтаксический сахар для абстрактных классов; то есть с помощью общего (будь то общие Java или C#) вы программируете на основе точно определенных интерфейсов и обычно оплачиваете стоимость вызовов виртуальных функций и/или динамических приведений для использования аргументов.
Шаблоны поддерживают общое программирование, метапрограммирование шаблонов и т.д. Благодаря сочетанию таких функций, как целочисленные аргументы шаблона, специализация и единообразная обработка встроенных и определяемых пользователем типов. Результатом является гибкость, универсальность и производительность, не сравнимые с "общими". STL является ярким примером.

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

Могу ли я вызвать исключение из конструктора? А из деструктора?

Примеры и подробные объяснения см. Язык Программирования C++ в Приложении E.
Есть одно предостережение: исключения нельзя использовать для некоторых проектов в реальном времени. Например, См. стандарты кодирования JSF летательный аппарат C++.

Почему C++ не предоставляет конструкцию "finally"?

Потому что C++ поддерживает альтернативу, которая почти всегда лучше: метод "получение ресурсов - инициализация" (TC++PL3, раздел 14.4). Основная идея состоит в том, чтобы представить ресурс локальным объектом, чтобы деструктор локального объекта освободил ресурс. Таким образом, программист не может забыть освободить ресурс. Например:

	class File_handle {
		FILE* p;
	public:
		File_handle(const char* n, const char* a)
			{ p = fopen(n,a); if (p) throw Open_error(errno); }
		File_handle(FILE* pp)
			{ p = pp; if (p) throw Open_error(errno); }

		~File_handle() { fclose(p); }

		operator FILE*() { return p; }

		// ...
	};

	void f(const char* fn)
	{
		File_handle f(fn,"rw");	// open fn for reading and writing
		// use file through f
	}

В системе нам нужен класс "дескриптор ресурса" для каждого ресурса. Однако нам не обязательно иметь пункт " finally" для каждого приобретения ресурса. В реалистичных системах гораздо больше приобретений ресурсов, чем видов ресурсов, поэтому метод "получение ресурсов - инициализация" приводит к меньшему количеству кода, чем использование конструкции "finally".
Кроме того, взгляните на примеры управления ресурсами в Приложении E к языку программирования C++.

Что такое auto_ptr и почему нет auto_array?

auto_ptr - это пример очень простого класса дескриптора, определенного в , поддерживающего безопасность исключений с использованием метода инициализации сбора ресурсов. auto_ptr содержит указатель, может использоваться в качестве указателя и удаляет объект, на который указано в конце его области видимости. Например:

	#include<memory>
	using namespace std;

	struct X {
		int m;
		// ..
	};

	void f()
	{
		auto_ptr<X> p(new X);
		X* q = new X;

		p->m++;		// use p just like a pointer
		q->m++;
		// ...

		delete q;
	}

Если в части …, объект, удерживаемый p, правильно удаляется деструктором auto_ptr, в то время как X, на который указывает q, утекает. Подробности см. TC++PL в разделе 14.4.2.
Auto_ptr - это очень легкий класс. В частности, это *не* указатель с подсчетом ссылок. Если вы "копируете" один auto_ptr в другой, присвоенный в auto_ptr содержит указатель, а присвоенный auto_ptr содержит 0. Например:

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

	struct X {
		int m;
		// ..
	};

	int main()
	{
		auto_ptr<X> p(new X);
		auto_ptr<X> q(p);
		cout << "p " << p.get() << " q " << q.get() << "\n";
	}

Результат должен быть указатель 0, за которым следует non-0 указатель. Например:

	p 0x0 q 0x378d0

auto_ptr::get() возвращает удерживаемый указатель.
Эта "семантика перемещения" отличается от обычной "семантики копирования" и может быть удивительной. В частности, никогда не используйте auto_ptr в качестве члена стандартного контейнера. Стандартные контейнеры требуют обычной семантики копирования. Например:

	std::vector<auto_ptr<X> >v;	// error

auto_ptr содержит указатель на отдельный элемент, а не указатель на массив:

	void f(int n)
	{
		auto_ptr<X> p(new X[n]);	// error
		// ...
	}

Это ошибка, потому что деструктор удалит указатель с помощью delete, а не delete[], и не сможет вызвать деструктор для последних n-1 Xs.
Итак, должны ли мы использовать auto_array для хранения массивов? Нет. Там нет auto_array. Причина в том, что в нем нет необходимости. Лучшим решением является использование вектора:

	void f(int n)
	{
		vector<X> v(n);
		// ...
	}

Если произойдет исключение в части …, деструктор v будет правильно вызван.
В C++11 используйте Unique_ptr вместо auto_ptr.

Для чего я не должен использовать исключения?

Исключения C++ предназначены для поддержки обработки ошибок. Используйте throw только для сигнализации об ошибке и catch только для указания действий по обработке ошибок. Существуют и другие варианты использования исключений, популярные на других языках, но не идиоматичные в C++ и намеренно плохо поддерживаемые реализациями C++ (эти реализации оптимизированы на основе предположения, что исключения используются для обработки ошибок).
В частности, throw - это не просто альтернативный способ возврата значения из функции (аналогично return). Это будет происходить медленно и приведет в замешательство большинство программистов на C++, привыкших видеть исключения, используемые только для обработки ошибок. Точно так же, throw не является хорошим способом выхода из цикла.

В чем разница между new и malloc()?

malloc() - это функция, которая принимает число (байт) в качестве аргумента; она возвращает значение void*, указывающее на неинициализированное хранилище. new - это оператор, который принимает тип и (необязательно) набор инициализаторов для этого типа в качестве своих аргументов; он возвращает указатель на (необязательно) инициализированный объект своего типа. Разница наиболее очевидна, когда вы хотите выделить объект пользовательского типа с нетривиальной семантикой инициализации. Примеры:

	class Circle : public Shape {
	public:
		Circle(Point c, int r);
		// no default constructor
		// ...
	};

	class X {
	public:
		X();	// default constructor
		// ...
	};

	void f(int n)
	{
		void* p1 = malloc(40);	// allocate 40 (uninitialized) bytes

		int* p2 = new int[10];	// allocate 10 uninitialized ints
		int* p3 = new int(10);	// allocate 1 int initialized to 10
		int* p4 = new int();	// allocate 1 int initialized to 0
		int* p4 = new int;	// allocate 1 uninitialized int

		Circle* pc1 = new Circle(Point(0,0),10); // allocate a Circle constructed
						         // with the specified argument
		Circle* pc2 = new Circle;	// error no default constructor

		X* px1 = new X;		// allocate a default constructed X 
		X* px2 = new X();	// allocate a default constructed X 
		X* px2 = new X[10];	// allocate 10 default constructed Xs 
		// ...
	}

Обратите внимание, что когда вы указываете инициализатор, используя обозначение "(value)", вы получаете инициализацию с этим значением. К сожалению, вы не можете указать это для массива. Часто вектор является лучшей альтернативой массиву, выделенному для свободного хранилища (например, учитывайте безопасность исключений).
Всякий раз, когда вы используете malloc(), вы должны учитывать инициализацию и преобразование возвращаемого указателя в соответствующий тип. Вам также придется подумать, правильно ли вы определили количество байтов для вашего использования. Нет никакой разницы в производительности между malloc() и new, если вы принимаете во внимание инициализацию.

malloc() сообщает об исчерпании памяти, возвращая 0. new сообщает об ошибках выделения и инициализации, вызывая исключения.

Объекты, созданные с помощью new, уничтожаются с помощью delete. Области памяти, выделенные malloc(), освобождаются с помощью free().

Могу ли я смешивать выделение и освобождение C стиль с C++ стилем?

Да, в том смысле, что вы можете использовать malloc() и new в одной и той же программе.
Нет, в том смысле, что вы не можете выделить объект с помощью malloc() и освободить его с помощью delete. Вы также не можете выделить с помощью new и delete с помощью free() или использовать realloc() для массива, выделенного с помощью new.

Операторы C++ new и delete гарантируют правильное построение и уничтожение; там, где необходимо вызвать конструкторы или деструкторы, они есть на месте. Функции в стиле C malloc(), calloc(), free() и realloc() этого не гарантируют. Кроме того, нет никакой гарантии, что механизм, используемый new и delete для получения и освобождения необработанной памяти, совместим с malloc() и free(). Если смешивание стилей работает в вашей системе, вам просто "повезло" - на данный момент.

Если вы чувствуете необходимость в realloc() - а многие так и делают, - тогда подумайте об использовании стандартного библиотечного вектора. Например

	// read words from input into a vector of strings:

	vector<string> words;
	string s;
	while (cin>>s && s!=".") words.push_back(s);

Вектор расширяется по мере необходимости.
Смотрите также примеры и обсуждение в разделе "Изучение стандартного C++ как нового языка", который вы можете скачать из моего списка публикаций.

Почему я должен использовать приведение для конвертирования из void*?

В С, вы можете неявно преобразовать void* в T*. Это небезопасно. Рассмотрим:

	#include<stdio.h>

	int main()
	{
		char i = 0;
		char j = 0;
		char* p = &i;
		void* q = p;
		int* pp = q;	/* unsafe, legal C, not C++ */

		printf("%d %d\n",i,j);
		*pp = -1;	/* overwrite memory starting at &i */
		printf("%d %d\n",i,j);
	}

Последствия использования T *, которое не указывает на T, могут быть катастрофическими. Следовательно, в C++, чтобы получить T* из void *, вам нужно явное приведение. Например, чтобы получить нежелательные эффекты описанной выше программы, вы должны написать:

		int* pp = (int*)q;

или используйте приведение нового стиля, чтобы сделать операцию преобразования непроверенного типа более заметной:

		int* pp = static_cast<int*>(q);

Отливки лучше избегать.
Одним из наиболее распространенных применений этого небезопасного преобразования в C является присвоение результата malloc() подходящему указателю. Например:

	int* p = malloc(sizeof(int));

В C++ используйте typesafe new оператор:

	int* p = new int;

Кстати, new оператор предлагает дополнительные преимущества по сравнению с malloc():

Например:

	typedef std::complex<double> cmplx;

	/* C style: */
	cmplx* p = (cmplx*)malloc(sizeof(int));	/* error: wrong size */
							/* forgot to test for p==0 */
	if (*p == 7) { /* ... */ }			/* oops: forgot to initialize *p */

	// C++ style:
	cmplx* q = new cmplx(1,2); // will throw bad_alloc if memory is exhausted
	if (*q == 7) { /* ... */ }

Как мне определить константу внутри класса?

Если вам нужна константа, которую вы можете использовать в постоянном выражении, скажем, в качестве привязки к массиву, у вас есть два варианта:

class X {
	static const int c1 = 7;
	enum { c2 = 19 };

	char v1[c1];
	char v2[c2];

	// ...
};

На первый взгляд объявление c1 кажется более чистым, но обратите внимание, что для использования этого синтаксиса инициализации в классе константа должна быть static const типа integral или enumeration, инициализированной постоянным выражением. Это довольно ограничительно:

class Y {
	const int c3 = 7;		// error: not static
	static int c4 = 7;		// error: not const
	static const float c5 = 7;	// error: not integral
};

Я склонен использовать "трюк enum", потому что он ортативный и не соблазняет меня использовать нестандартные расширения синтаксиса инициализации в классе.
Так почему же существуют эти неудобные ограничения? Класс обычно объявляется в заголовочном файле, а заголовочный файл обычно включается во многие единицы перевода. Однако, чтобы избежать сложных правил компоновщика, C++ требует, чтобы каждый объект имел уникальное определение. Это правило было бы нарушено, если бы C++ разрешал определение сущностей в классе, которые необходимо хранить в памяти как объекты. См. D&E для объяснения компромиссов в дизайне C++.
У вас больше гибкости, если const не требуется для использования в постоянном выражении:

	class Z {
		static char* p;		// initialize in definition
		const int i;		// initialize in constructor
	public:
		Z(int ii) :i(ii) { }
	};

	char* Z::p = "hello, there";

Вы можете взять адрес статического члена, если (и только если) у него есть внеклассовое определение:

	class AE {
		// ...
	public:
		static const int c6 = 7;
		static const int c7 = 31;
	};

	const int AE::c7;	// definition

	int f()
	{
		const int* p1 = &AE::c6;	// error: c6 not an lvalue
		const int* p2 = &AE::c7;	// ok
		// ...
	}

Почему delete не обнуляет свой операнд?

Рассмотрим

	delete p;
	// ...
	delete p;

Если часть... не касается p, тогда вторая "delete p;" является серьезной ошибкой, от которой реализация C++ не может эффективно защитить себя (без необычных мер предосторожности). Поскольку удаление нулевого указателя безвредно по определению, простым решением было бы для "delete p;" чтобы выполнить "p = 0;" после того, как он выполнил все остальное, что требуется. Однако C++ не гарантирует этого.
Одна из причин заключается в том, что delete операнда не обязательно должен быть значением lvalue. Рассмотрим:

	delete p+1;
	delete f(x);

Здесь реализация delete не имеет указателя, которому она может присвоить ноль. Эти примеры могут быть редкими, но они подразумевают, что невозможно гарантировать, что `любой указатель на удаленный объект равен 0". Более простой способ обойти это `правило" - иметь два указателя на объект:

	T* p = new T;
	T* q = p;
	delete p;
	delete q;	// ouch!

C++ явно позволяет реализации delete чтобы обнулять операнд lvalue, и я надеялся, что реализации сделают это, но эта идея, похоже, не стала популярной среди разработчиков.
Если вы считаете важным обнуление указателей, рассмотрите возможность использования функции destroy:

	template<class T> inline void destroy(T*& p) { delete p; p = 0; }

Подумайте об этом еще раз - еще одна причина свести к минимуму явное использование new и delete, полагаясь на стандартные библиотечные контейнеры, дескрипторы и т.д.

Обратите внимание, что передача указателя в качестве ссылки (чтобы разрешить обнуление указателя) имеет дополнительное преимущество, заключающееся в предотвращении вызова функции destroy() для rvalue:

	int* f();
	int* p;
	// ...
	destroy(f());	// error: trying to pass an rvalue by non-const reference
	destroy(p+1);	// error: trying to pass an rvalue by non-const reference

Почему деструктор не вызывается в конце области видимости?

Простой ответ - "конечно, это так!", Но взгляните на примеры, которые часто сопровождают этот вопрос:

	void f()
	{
		X* p = new X;
		// use p
	}

То есть существовало некоторое (ошибочное) предположение, что объект, созданный с помощью "new", будет уничтожен в конце функции.
В принципе, вы должны использовать "new" только в том случае, если хотите, чтобы объект жил дольше срока службы области, в которой вы его создаете. После этого вам нужно использовать "delete", чтобы уничтожить его. Например:

	X* g(int i) { /* ... */ return new X(i); }	// the X outlives the call of g()

	void h(int i)
	{
		X* p = g(i);
		// ...
		delete p;
	}

Если вы хотите, чтобы объект находился только в области видимости, не используйте "new", а просто определите переменную:

        {
                ClassName x;
                // use x
        }

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

	void fct()	// ugly, error-prone, and inefficient
	{
		X* p = new X;
		// use p
		delete p;
	}

Могу ли я написать "void main()"?

Определение

	void main() { /* ... */ }

не является и никогда не был C++, и даже не был C. См. ISO C++ стандарт 3.6.1[2] или ISO C стандарт 5.1.2.2.1. Соответствующая реализация принимает

	int main() { /* ... */ }

и

	int main(int argc, char* argv[]) { /* ... */ }

Соответствующая реализация может предоставлять больше версий main(), но все они должны иметь возвращаемый тип int. Int, возвращаемый main(), - это способ для программы вернуть значение "системе", которая его вызывает. В системах, которые не предоставляют такой возможности, возвращаемое значение игнорируется, но это не делает "void main()" законным C++ или законным C. Даже если ваш компилятор принимает "void main()", избегайте этого или рискуйте быть сочтенными невежественными программистами C и C++.
В C++ main() не обязательно должен содержать явный оператор return. В этом случае возвращаемое значение равно 0, что означает успешное выполнение. Например:

	#include<iostream>

	int main()
	{
		std::cout << "This program returns the integer value 0\n";
	}

Обратите также внимание, что ни ISO C++, ни C99 не позволяют вам исключать тип из объявления. То есть, в отличие от C89 и ARM C++, "int" не предполагается там, где тип отсутствует в объявлении. Следовательно:

	#include<iostream>

	main() { /* ... */ }

это ошибка, потому что тип возвращаемого значения main() отсутствует.

Почему я не могу перегрузить точку, ::, sizeof и т.д.?

Большинство операторов могут быть перегружены программистом. Исключениями являются

	. (dot)  ::  ?:  sizeof

Нет никакой фундаментальной причины запрещать перегрузку ?:. Я просто не видел необходимости вводить особый случай перегрузки троичного оператора. Обратите внимание, что функция, перегружающая expr1?expr2:expr3, не сможет гарантировать, что был выполнен только один из expr2 и expr3.
Sizeof не может быть перегружен, поскольку встроенные операции, такие как увеличение указателя на массив, неявно зависят от него. Рассмотрим:

	X a[10];
	X* p = &a[3];
	X* q = &a[3];
	p++;	// p points to a[4]
		// thus the integer value of p must be
		// sizeof(X) larger than the integer value of q

Таким образом, sizeof(X) не мог быть придан программистом новому и иному значению без нарушения основных языковых правил.
В N::m ни N, ни m не являются выражениями со значениями; N и m - это имена, известные компилятору, и :: выполняет разрешение области видимости (во время компиляции), а не вычисление выражения. Можно было бы представить, что можно разрешить перегрузку x::y, где x является объектом, а не пространством имен или классом, но это, вопреки первому представлению, повлекло бы за собой введение нового синтаксиса (чтобы разрешить expr::expr). Не очевидно, какие выгоды принесет такое усложнение.

Оператор . (точка) в принципе может быть перегружена с использованием того же метода, что и для ->. Однако это может привести к возникновению вопросов о том, предназначена ли операция для перегрузки объекта . или объект на который ссылается . Например:

	class Y {
	public:
		void f();
		// ...
	};

	class X {	// assume that you can overload .
		Y* p;
		Y& operator.() { return *p; }
		void f();
		// ...
	};

	void g(X& x)
	{
		x.f();	// X::f or Y::f or error?
	}

Эту проблему можно решить несколькими способами. Во время стандартизации не было очевидно, какой способ будет наилучшим. Для получения более подробной информации см. раздел D&E.

Могу ли я определить свои собственные операторы?

К сожалению, нет. Такая возможность рассматривалась несколько раз, но каждый раз я/мы решали, что вероятные проблемы перевешивают вероятные выгоды.
Это не языковая техническая проблема. Даже когда я впервые задумался об этом в 1983 году, я знал, как это можно реализовать. Однако мой опыт показывает, что, когда мы выходим за рамки самых тривиальных примеров, люди, похоже, имеют несколько разные мнения об "очевидном" значении использования оператора. Классический пример - a**b**c. Предположим, что ** было сделано для обозначения возведения в степень. Теперь должно ли a**b**c означать (a**b)**c или a**(b**c)? Я думал, что ответ очевиден, и мои друзья согласились - а потом мы обнаружили, что не пришли к единому мнению о том, какое решение было очевидным. Мое предположение заключается в том, что такие проблемы привели бы к незначительным ошибкам.

Как мне конвертировать целое число в строковый тип?

Самый простой способ - использовать stringstream:

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

	string itos(int i)	// convert int to string
	{
		stringstream s;
		s << i;
		return s.str();
	}

	int main()
	{
		int i = 127;
		string ss = itos(i);
		const char* p = ss.c_str();

		cout << ss << " " << p << "\n";
	}

Естественно, этот метод работает для конвертирования любого типа, который вы можете вывести с помощью << в строку. Описание потоков строк см. Язык программирования C++ в разделе 21.5.3. 

Как мне вызвать функцию C из C++?

Просто объявите функцию C ``extern "C"'' (в вашем коде C++) и вызовите ее (из вашего кода C или C++). Например:

	// C++ code

	extern "C" void f(int);	// one way

	extern "C" {	// another way
		int g(double);
		double h();
	};

	void code(int i, double d)
	{
		f(i);
		int ii = g(d);
		double dd = h();
		// ...
	}

Определения функций могут выглядеть следующим образом:

	/* C code: */

	void f(int i)
	{
		/* ... */
	}

	int g(double d)
	{
		/* ... */
	}

	double h()
	{
		/* ... */
	}

Обратите внимание, что используются правила типа C++, а не правила C. Таким образом, вы не можете вызвать функцию, объявленную как ``extern "C"'', с неправильным числом аргументов. Например:

	// C++ code

	void more_code(int i, double d)
	{
		double dd = h(i,d);	// error: unexpected arguments
		// ...
	}

Как мне вызвать функцию C++ из C?

Просто объявите функцию C++ ``extern "C"'' (в вашем коде C++) и вызовите ее (из вашего кода C или C++). Например:

	// C++ code:

	extern "C" void f(int);

	void f(int i)
	{
		// ...
	}

Теперь f() можно использовать следующим образом:

	/* C code: */

	void f(int);
	
	void cc(int i)
	{
		f(i);
		/* ... */
	}

Естественно, это работает только для функций, не являющихся членами. Если вы хотите вызвать функции-члены (вкл. виртуальные функции) из C, вам нужно предоставить простую оболочку. Например:

	// C++ code:

	class C {
		// ...
		virtual double f(int);
	};

	extern "C" double call_C_f(C* p, int i)	// wrapper function
	{
		return p->f(i);
	}

Теперь C::f() можно использовать следующим образом:

	/* C code: */

	double call_C_f(struct C* p, int i);
	
	void ccc(struct C* p, int i)
	{
		double d = call_C_f(p,i);
		/* ... */
	}

Если вы хотите вызывать перегруженные функции из C, вы должны предоставить оболочки с различными именами для использования кода C. Например:

	// C++ code:

	void f(int);
	void f(double);

	extern "C" void f_i(int i) { f(i); }
	extern "C" void f_d(double d) { f(d); }

Теперь функции f() можно использовать следующим образом:

	/* C code: */

	void f_i(int);
	void f_d(double);
	
	void cccc(int i,double d)
	{
		f_i(i);
		f_d(d);
		/* ... */
	}

Обратите внимание, что эти методы можно использовать для вызова библиотеки C++ из кода C, даже если вы не можете (или не хотите) изменять заголовки C++.

Который из них правильно: ``int* p;'' или ``int *p;''?

Оба являются "правильными" в том смысле, что оба являются допустимыми C и C++, и оба имеют совершенно одинаковое значение. Что касается определений языка и компиляторов, мы могли бы с таким же успехом сказать ``int*p;'' или ``int * p;''
Выбор между ``int* p;'' и ``int *p;'' зависит не от правильного и неправильного, а от стиля и акцента. C подчеркнутые выражения; заявления часто считались не более чем необходимым злом. C++, с другой стороны, уделяет большое внимание типам.

`Типичный программист C" пишет ``int *p;'' и объясняет это ``*p - это то, что является int", подчеркивая синтаксис, и может указывать на грамматику объявления C (и C++), чтобы аргументировать правильность стиля. Действительно, * привязывается к имени p в грамматике.

`Типичный программист C++" пишет ``int* p;''  и объясняет это тем, что ``p - это указатель на int", подчеркивая тип. Действительно, тип p - int*. Я явно предпочитаю этот акцент и считаю его важным для правильного использования более продвинутых частей C++.

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

	int* p, p1;	// probable error: p1 is not an int*

Размещение * ближе к имени не делает такого рода ошибки значительно менее вероятными.

	int *p, p1;	// probable error?

Объявление одного имени для каждого объявления сводит проблему к минимуму - в частности, когда мы инициализируем переменные. Люди гораздо реже пишут:

	int* p = &i;
	int p1 = p;	// error: int initialized by int*

И если они это сделают, компилятор будет жаловаться.
Всякий раз, когда что-то можно сделать двумя способами, кто-то будет сбит с толку. Всякий раз, когда что-то является вопросом вкуса, дискуссии могут затянуться навсегда. Придерживайтесь одного указателя на объявление и всегда инициализируйте переменные, и источник путаницы исчезнет. Более подробное обсуждение синтаксиса объявления C см. Дизайн и Эволюция C++.

Какой стиль разметки лучше всего подходит для моего кода?

Такие вопросы стиля - это вопрос личного вкуса. Часто мнения о компоновке кода сильно разделяются, но, вероятно, согласованность имеет большее значение, чем какой-либо конкретный стиль. Как и большинству людей, мне было бы трудно привести убедительные логические аргументы в пользу своих предпочтений.
Лично я использую то, что часто называют стилем "K&R". Когда вы добавляете соглашения для конструкций, не найденных в C, это становится тем, что иногда называют стилем " Stroustrup". Например:

class C : public B {
public:
	// ...
};

void f(int* p, int max)
{
	if (p) {
		// ...
	}

	for (int i = 0; i<max; ++i) {
		// ...
	}
}

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

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

Как вам называть переменные? Вы рекомендуете "Венгерский"?

Нет, я не рекомендую "венгерский". Я считаю "венгерский" (встраивание сокращенной версии типа в имя переменной) методом, который может быть полезен в нетипизированных языках, но совершенно не подходит для языка, который поддерживает универсальное программирование и объектно-ориентированное программирование - оба из которых подчеркивают выбор операций на основе тип аргументов (известен языку или поддержке во время выполнения). В этом случае "встраивание типа объекта в имена" просто усложняет и сводит к минимуму абстракцию. В той или иной степени у меня возникают аналогичные проблемы с каждой схемой, которая встраивает информацию о технических деталях языка (например, область действия, класс хранения, синтаксическая категория) в имена. Я согласен с тем, что в некоторых случаях создание подсказок типа в именах переменных может быть полезным, но в целом, и особенно по мере развития программного обеспечения, это становится опасностью для обслуживания и серьезным ущербом для хорошего кода. Избегайте этого, как чумы.
Итак, мне не нравится называть переменную по ее типу; что мне нравится и что я рекомендую? Назовите переменную (функцию, тип, что угодно) в зависимости от того, что она собой представляет или делает. Выберите значимое имя; то есть выбирайте имена, которые помогут людям понять вашу программу. Даже у вас возникнут проблемы с пониманием того, что должна делать ваша программа, если вы будете засорять ее переменными с простыми для ввода именами, такими как x1, x2, s3, и p7. Аббревиатуры и сокращения могут сбить людей с толку, поэтому используйте их экономно. Сокращения следует использовать экономно. Рассмотрим mtbf, TLA, myw, RTFM, и NBV. Они очевидны, но подождите несколько месяцев, и даже я забуду хотя бы об одном.

Короткие имена, такие как x и i, имеют смысл, когда используются традиционно; то есть x должно быть локальной переменной или параметром, а i должно быть индексом цикла.

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

	partial_sum    element_count    staple_partition

Они, вероятно, слишком длинные:

	the_number_of_elements    remaining_free_slots_in_symbol_table

Я предпочитаю использовать подчеркивания для разделения слов в идентификаторе (например, element_count), а не альтернативы, такие как elementCount и ElementCount. Никогда не используйте имена со всеми заглавными буквами (например, BEGIN_TRANSACTION), потому что это обычно зарезервировано для макросов. Даже если вы не используете макросы, кто-то мог засорить ими ваши заголовочные файлы. Используйте начальную заглавную букву для типов (например, Square и Graph). Язык C++ и стандартная библиотека не используют заглавные буквы, поэтому это int, а не Int, и string, а не String. Таким образом, вы сможете распознавать стандартные типы.
Избегайте имен, которые легко ввести неправильно, неправильно истолковать или перепутать. Например

	name    names    nameS
	foo     f00
	fl      f1       fI       fi

Символы 0, o, O, 1, l, и I особенно склонны вызывать проблемы.
Часто ваш выбор соглашений об именовании ограничен местными правилами стиля. Помните, что поддержание последовательного стиля часто важнее, чем выполнение каждой мелочи так, как вы считаете наилучшим образом.

Должен ли я поставить "const" до или после типа?

Я уже говорил об этом раньше, но это дело вкуса. "const T" и "T const" были - и будут - (оба) разрешены и эквивалентны. Например:

	const int a = 1;	// ok
	int const b = 2;	// also ok

Я предполагаю, что использование первой версии приведет в замешательство меньшее количество программистов (``более идиоматично").

Почему? Когда я изобрел "const" (первоначально названный "readonly" и имевший соответствующий "writeonly"), я позволил ему идти до или после типа, потому что я мог сделать это без двусмысленности. Предстандартные C и C++ накладывали несколько (если таковые имелись) правил упорядочения на спецификаторы.

Я не помню никаких глубоких мыслей или сложных дискуссий об этом заказе в то время. Нескольким из первых пользователей - особенно мне - просто понравился внешний вид

	const int c = 10;

лучше, чем

	int const c = 10;

в это время.

Возможно, на меня повлиял тот факт, что мои самые ранние примеры были написаны с использованием " readonly" и

	readonly int c = 10;

читается лучше, чем

	int readonly c = 10;

Самый ранний (C или C++) код, использующий "const", по-видимому, был создан (мной) путем глобальной замены "const" на " readonly".
Я помню, как обсуждал альтернативы синтаксиса с несколькими людьми, в том числе. Деннис Ричи - но я не помню, на каких языках я смотрел.

Обратите внимание, что в указателях const, "const" всегда следует после "*". Например:

	int *const p1 = q;	// constant pointer to int variable
	int const* p2 = q;	// pointer to constant int
	const int* p3 = q;	// pointer to constant int

Что хорошего в static_cast?

Cast, как правило, лучше избегать. За исключением dynamic_cast, их использование подразумевает возможность ошибки типа или усечения числового значения. Даже невинный на вид cast может стать серьезной проблемой, если во время разработки или обслуживания будет изменен один из задействованных типов. Например, что это значит?:

	x = (T)y;

Мы не знаем. Это зависит от типа T и типов x и y. T может быть именем класса, typedef или, возможно, параметром шаблона. Возможно, x и y являются скалярными переменными, а (T) представляет собой преобразование значений. Возможно, x относится к классу, производному от класса y, и (T) является подавленным. Возможно, x и y являются несвязанными типами указателей. Поскольку приведение в стиле C (T) может использоваться для выражения множества логически различных операций, у компилятора есть лишь малейший шанс обнаружить злоупотребления. По той же причине программист может не знать точно, что делает приведение. Это иногда считается преимуществом начинающими программистами и является источником незначительных ошибок, когда новичок ошибается.
"Приведения в новом стиле" были введены для того, чтобы дать программистам возможность более четко сформулировать свои намерения и чтобы компилятор улавливал больше ошибок. Например:

	int a = 7;
	double* p1 = (double*) &a;			// ok (but a is not a double)
	double* p2 = static_cast<double*>(&a);	// error
	double* p2 = reinterpret_cast<double*>(&a);	// ok: I really mean it

	const int c = 7;
	int* q1 = &c;			// error
	int* q2 = (int*)&c;		// ok (but *q2=2; is still invalid code and may fail)
	int* q3 = static_cast<int*>(&c);	// error: static_cast doesn't cast away const
	int* q4 = const_cast<int*>(&c);	// I really mean it

Идея заключается в том, что конвертирования, разрешенные static_cast, с меньшей вероятностью приведут к ошибкам, чем те, которые требуют reinterpret_cast. В принципе, можно использовать результат static_cast без приведения его обратно к исходному типу, тогда как вы всегда должны приводить результат reinterpret_cast обратно к исходному типу перед его использованием для обеспечения переносимости.
Вторичной причиной введения cast в новом стиле было то, что cast в стиле C очень трудно обнаружить в программе. Например, вы не можете удобно искать cast с помощью обычного редактора или текстового процессора. Эта почти невидимость cast в стиле C особенно прискорбна, потому что они настолько потенциально опасны. Уродливая операция должна иметь уродливую синтаксическую форму. Это наблюдение было одной из причин выбора синтаксиса для cast в новом стиле. Еще одна причина заключалась в том, чтобы cast в новом стиле соответствовали обозначению шаблона, чтобы программисты могли писать свои собственные cast, особенно проверенные во время выполнения.

Может быть, из-за того, что static_cast настолько уродлив и относительно сложен для ввода, вы, скорее всего, дважды подумаете, прежде чем использовать его? Это было бы хорошо, потому что cast действительно в основном можно избежать в современном C++.

Так, что плохого в использовании макросов?

Макросы не подчиняются правилам области видимости и типов C++. Это часто является причиной тонких и не очень тонких проблем. Следовательно, C++ предоставляет альтернативы, которые лучше сочетаются с остальной частью C++, такие как встроенные функции, шаблоны и пространства имен.
Рассмотрим:

	#include "someheader.h"

	struct S {
		int alpha;
		int beta;
	};

Если кто-то (неразумно) написал макрос с именем "альфа" или макрос с именем "бета", это может не скомпилироваться или (что еще хуже) скомпилироваться во что-то неожиданное. Например, "someheader.h" может содержать:

	#define alpha 'a'
	#define beta b[2]

Такие соглашения, как наличие макросов (и только макросов) во ВСЕХ заглавных буквах, помогают, но защита от макросов на уровне языка отсутствует. Например, тот факт, что имена членов находились в области видимости структуры, не помог: макросы работают с программой как поток символов до того, как ее увидит сам компилятор. Это, кстати, является основной причиной того, что среды и инструменты разработки программ на C и C++ были простыми: человек и компилятор видят разные вещи.
К сожалению, вы не можете предполагать, что другие программисты постоянно избегают того, что вы считаете "действительно глупым". Например, кто-то недавно сообщил мне, что они столкнулись с макросом, содержащим goto. Я тоже это видел и слышал аргументы, которые могли бы - в момент слабости - показаться разумными. Например:

	#define prefix get_ready(); int ret__
	#define Return(i) ret__=i; do_something(); goto exit
	#define suffix exit: cleanup(); return ret__

	int f()
	{
		prefix;
		// ...
		Return(10);
		// ...
		Return(x++);
		//...
		suffix;
	}

Представьте, что вам это преподносят как программисту технического обслуживания; "скрытие" макросов в заголовке - что нередко бывает - затрудняет обнаружение такого рода "магии".
Одна из наиболее распространенных тонких проблем заключается в том, что макрос в стиле функции не подчиняется правилам передачи аргументов функции. Например:

	#define square(x) (x*x)

	void f(double d, int i)
	{
		square(d);	// fine
		square(i++);	// ouch: means (i++*i++)
		square(d+1);	// ouch: means (d+1*d+1); that is, (d+d+1)
		// ...
	}

Проблема "d+1" решается путем добавления круглых скобок в "вызове" или в определении макроса:

	#define square(x) ((x)*(x))	/* better */

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

Как вы произносите "cout"?

"cout" произносится как " see-out". "c" означает "символ", потому что iostreams сопоставляет значения с байтовыми (символьными) представлениями и из них.

Как вы произносите "char"?

"char" обычно произносится как "tchar", а не как "kar". Это может показаться нелогичным, потому что " character" произносится как " ka-rak-ter", но никто никогда не обвинял английское произношение (pronunciation не " pronounciation" :-) и правописание в том, что они логичны.