Нет любви C++, когда речь идет о "скрытых особенностях" ряда вопросов? Подумал, что брошу его туда. Какие скрытые возможности C++?
@Laith J: Не так много людей прочитали 786-страничный стандарт ISO C++ от корки до корки, но я полагаю, что вы читали и сохранили все это, верно?
@Laith, @j_random: См. Мой вопрос «Что такое шутка программиста, как ее распознать и каков правильный ответ» на stackoverflow.com/questions/1/you-have-been-link-rolled.
См. Сообщения meta.stackexchange.com/questions/56669/…, meta.stackexchange.com/questions/57226/… и связанные с ними мета-сообщения.





Я не уверен насчет скрытого, но есть некоторые интересно'ухищрения', которые, вероятно, не очевидны после простого чтения спецификации.
Даже если он старше на 55 минут, его следует объединить / объединить с дубликатом в stackoverflow.com/questions/75538/hidden-features-of-c/… или просто удалить в пользу другого.
Пожалуйста ... ПОЖАЛУЙСТА, не используйте Устройство Даффа.
C++ is a standard, there shouldn't be any hidden features...
C++ - это многопарадигмальный язык, вы можете поставить последние деньги на наличие скрытых функций. Один пример из многих: метапрограммирование шаблона. Никто в комитете по стандартам не намеревался создать полный по Тьюрингу подъязык, который будет выполняться во время компиляции.
Я нашел этот блог прекрасным ресурсом о секретах C++: Истины C++.
Здесь нет скрытых функций, но язык C++ очень мощный, и часто даже разработчики стандартов не могли представить, для чего можно использовать C++.
На самом деле, используя достаточно простую языковую конструкцию, вы можете написать что-нибудь очень мощное. Многие из таких вещей доступны на www.boost.org в качестве примеров (и http://www.boost.org/doc/libs/1_36_0/doc/html/lambda.html среди них).
Чтобы понять, как простая языковая конструкция может быть объединена с чем-то мощным, полезно прочитать "Шаблоны C++: полное руководство" Дэвида Вандевурда, Николая М. Йосаттиса и действительно волшебную книгу «Современный дизайн на C++ ...» Андрея Александреску.
И, наконец, С ++ сложно выучить, надо попробовать его залить;)
Есть много «неопределенного поведения». Вы можете узнать, как избежать чтения хороших книг и стандартов.
Как это особенность? Есть два разных способа рассматривать это как функцию, но я не вижу здесь никаких указаний на это. -1 за расплывчатость.
Или вы можете научиться принимать их, что хорошо.
В C++ существует масса «хитрых» конструкций. Они идут от «простых» реализаций закрытые / финальные классы с использованием виртуального наследования. И переходите к довольно «сложным» конструкциям метапрограммирования, таким как MPL (руководство) Boost. Возможности для стрельбы себе в ногу безграничны, но если их контролировать (например, опытные программисты), они обеспечивают лучшую гибкость с точки зрения ремонтопригодности и производительности.
One example out of many: template metaprogramming. Nobody in the standards committee intended there to be a Turing-complete sublanguage that gets executed at compile-time.
Метапрограммирование шаблонов вряд ли является скрытой функцией. Это даже в библиотеке наддува. См. MPL. Но если «почти скрыто» достаточно, взгляните на увеличить библиотеки. Он содержит много полезных вещей, которые нелегко получить без поддержки сильной библиотеки.
Одним из примеров является библиотека boost.lambda, которая интересна, поскольку C++ не имеет лямбда-функций в текущем стандарте.
Другой пример - Локи, который «широко использует метапрограммирование шаблонов C++ и реализует несколько часто используемых инструментов: список типов, функтор, синглтон, интеллектуальный указатель, фабрику объектов, посетитель и мультиметоды». [Википедия]
Метапрограммирование шаблонов больше не скрывается, потому что оно было очень полезным. Однако это скрыто в том, что эта функция не разработана на C++, а появилась случайно.
О времени жизни временных файлов, привязанных к константным ссылкам, мало кто знает. Или, по крайней мере, это моя любимая часть знаний о C++, о которой большинство людей не знает.
const MyClass& x = MyClass(); // temporary exists as long as x is in scope
Вы можете уточнить? Как это ты просто дразнишь;)
ScopeGuard (ddj.com/cpp/184403758) - отличный пример, который использует эту функцию.
Я с Джозефом Гарвином. Пожалуйста, просветите нас.
Я только что сделал в комментариях. Кроме того, это естественное следствие использования константного ссылочного параметра.
Я прочитал ответ и ссылку на ScopeGuard и до сих пор не понимаю, что это значит.
@ Дуб: stackoverflow.com/questions/256724/…
Чем это отличается от: const MyClass x; ?
@Ferruccio, в этом случае он идентичен, но вы можете использовать производный класс, и его время жизни по-прежнему будет расширено до области действия ссылки const.
Оператор массива ассоциативен.
A [8] является синонимом * (A + 8). Поскольку сложение ассоциативно, его можно переписать как * (8 + A), что является синонимом ..... 8 [A]
Вы не сказали, что полезно ... :-)
На самом деле, используя этот трюк, вы действительно должны обращать внимание на то, какой тип вы используете. A [8] на самом деле является 8-м A, тогда как 8 [A] - это целое число Ath, начинающееся с адреса 8. Если A - это байт, у вас есть ошибка.
Вы имеете в виду "коммутативный", когда говорите "ассоциативный"?
Винсент, ты ошибаешься. Тип A вообще не имеет значения. Например, если бы A был char*, код все равно был бы действителен.
Помните, что A должен быть указателем, а не оператором перегрузки класса [].
Винсент, здесь должен быть один интегральный тип и один тип указателя, и ни C, ни C++ не заботятся о том, какой из них идет первым.
A [8] соответствует *(A + 8). 8 [A] соответствует *(8 + A). Они одинаковые.
Большинство разработчиков C++ игнорируют возможности метапрограммирования шаблонов. Проверьте Loki Libary. Он реализует несколько расширенных инструментов, таких как список типов, функтор, синглтон, интеллектуальный указатель, фабрика объектов, посетитель и мультиметоды, широко использующие метапрограммирование шаблонов (из википедия). По большей части вы можете рассматривать их как «скрытую» функцию C++.
Мы не игнорируем это, мы избегаем этого ....
Дубликат метапрограммирования шаблона из stackoverflow.com/questions/75538/hidden-features-of-c/…. Эти примеры объединены в ответ, поэтому дубликат можно удалить.
Я согласен с большинством постов там: C++ - это язык с множеством парадигм, поэтому «скрытые» функции, которые вы найдете (кроме «неопределенного поведения», которого вам следует избегать любой ценой), - это умное использование возможностей.
Большинство из этих возможностей не являются встроенными функциями языка, а основаны на библиотеках.
Наиболее важным является RAII, который часто игнорируется в течение многих лет разработчиками C++ из мира C. Перегрузка оператора часто неправильно понимается функцией, которая обеспечивает как поведение, подобное массиву (оператор индекса), операции, подобные указателям (интеллектуальные указатели), так и операции, подобные встроенным (умножение матриц.
Использование исключение часто затруднено, но при некоторой работе можно получить действительно надежный код с помощью спецификаций безопасность исключений (включая код, который не будет терпеть неудачу, или который будет иметь функции, подобные фиксации, которые будут успешными, или вернуться к своему исходное состояние).
Самая известная из «скрытых» функций C++ - это метапрограммирование шаблона, поскольку она позволяет вам частично (или полностью) выполнять вашу программу во время компиляции, а не во время выполнения. Однако это сложно, и вы должны хорошо разбираться в шаблонах, прежде чем пробовать это делать.
Другие используют множественную парадигму для создания «способов программирования» вне предка C++, то есть C.
Используя функторы, вы можете моделировать функции с дополнительной безопасностью типов и с отслеживанием состояния. Используя шаблон команда, вы можете отложить выполнение кода. Большинство других шаблоны проектирования могут быть легко и эффективно реализованы на C++ для создания альтернативных стилей кодирования, которые не должны входить в список «официальных парадигм C++».
Используя шаблоны, вы можете создать код, который будет работать с большинством типов, включая не тот, который вы думали вначале. Вы также можете повысить безопасность типов (например, автоматический тип malloc / realloc / free). Возможности объектов C++ действительно мощные (и, следовательно, опасны при неосторожном использовании), но даже динамический полиморфизм имеет свою статическую версию в C++: CRTP.
Я обнаружил, что большинство книг типа "Эффективный C++" от Скотта Мейерса или книг типа "Исключительный C++" от Херба Саттера одновременно легко читаются и представляют собой кладезь информации об известных и менее известных особенностях C++.
Среди моих предпочтений есть тот, который должен вызвать ужас у любого Java-программиста: в C++ это наиболее объектно-ориентированный способ добавить функцию к объекту - использовать функцию, не являющуюся членом, не являющуюся другом, вместо функции-члена (т.е. метод класса), потому что:
В C++ интерфейс класса - это как его функции-члены, так и функции, не являющиеся членами, в одном и том же пространстве имен.
не являющиеся друзьями функции, не являющиеся членами, не имеют привилегированного доступа к внутреннему классу. Таким образом, использование функции-члена вместо не-члена, не являющегося другом, ослабит инкапсуляцию класса.
Это не перестает удивлять даже опытных разработчиков.
(Источник: среди прочего, онлайн-гуру недели Херба Саттера № 84: http://www.gotw.ca/gotw/084.htm)
+1 очень обстоятельный ответ. он неполный по очевидным причинам (иначе больше не было бы «скрытых функций»!): p в первом пункте в конце ответа вы упомянули члены интерфейса класса. Вы имеете в виду, что ".. это и его функции-члены, и функции, не являющиеся членами друг"?
CRTP - en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern
то, о чем вы говорите с 1, должно быть поиском koenig, верно?
@wilhelmtell: Нет-нет-нет ... :-p ... Я ДЕЙСТВИТЕЛЬНО имею в виду "его функции-члены и НЕЧЛЕННЫЕ функции, не являющиеся членами" .... Поиск Кенига гарантирует, что эти функции будут рассмотрены раньше, чем другие " вне "функций в поиске символов
@Comptrol: Вы почти правы, это последствие поиска Кеонига.
Отличный пост, и +1 особенно к последней части, о чем мало кто догадывается. Я бы, наверное, добавил библиотеку Boost как «скрытую функцию». Я в значительной степени считаю это стандартной библиотекой, которую должен был иметь C++. ;)
Ооо, вместо этого я могу составить список ненависти к животным:
С положительной стороны
домашняя ненависть: нет определенного ABI для приложений C++, в отличие от приложений C, которые все используют, потому что каждый язык может гарантировать вызов функции C, никто не может сделать то же самое для C++.
Деструкторы должны быть виртуальными только в том случае, если вы намереваетесь уничтожать полиморфно, что немного отличается от первого пункта.
В C++ 0x локальные типы могут использоваться в качестве параметров шаблона.
В C++ 0x деструкторы будут виртуальными, если у объекта есть какие-либо виртуальные функции (например, vtable).
не забывайте NRVO, и, конечно, разрешена любая оптимизация, если она не меняет вывод программы.
Спецификации исключений C++ 0x устарели по сравнению со стандартом.
Одна языковая функция, которую я считаю несколько скрытой, потому что я никогда не слышал о ней за все время учебы в школе, - это псевдоним пространства имен. Это не было доведено до моего сведения, пока я не встретил его примеры в документации по усилению. Конечно, теперь, когда я знаю об этом, вы можете найти его в любом стандартном справочнике по C++.
namespace fs = boost::filesystem;
fs::path myPath( strPath, fs::native );
Думаю, это полезно, если вы не хотите использовать using.
Это также полезно как способ переключения между реализациями, будь то выбор, скажем, потокобезопасный или небезопасный, или версия 1 против 2.
Это особенно полезно, если вы работаете над очень большим проектом с большими иерархиями пространств имен и не хотите, чтобы ваши заголовки вызывали загрязнение пространства имен (и вы хотите, чтобы объявления ваших переменных были удобочитаемыми).
Вы можете без ошибок помещать URI в исходный код C++. Например:
void foo() {
http://stackoverflow.com/
int bar = 4;
...
}
Но я подозреваю, что только по одному на каждую функцию? :)
Это ошибка? Что он должен делать? Я попробовал, и он действительно скомпилировался без ошибок; Я получил предупреждение: предупреждение C4102: 'http': метка без ссылки
Только не пытайтесь добавить более одного на исходный файл: P
@jpoh: http, за которым следует двоеточие, становится «меткой», которую вы позже используете в операторе goto. вы получаете это предупреждение от своего компилятора, потому что оно не используется ни в одном операторе goto в приведенном выше примере.
Вы можете добавить более одного, если у них разные протоколы! ftp.microsoft.com gopher: //aerv.nl/1 и так далее ...
@Pavel: идентификатор, за которым следует двоеточие, является меткой (для использования с goto, который есть в C++). Все, что следует за двумя косыми чертами, является комментарием. Следовательно, с http://stackoverflow.comhttp является меткой (теоретически можно написать goto http;), а //stackoverflow.com - просто комментарием в конце строки. Оба они являются законным C++, поэтому конструкция компилируется. Конечно, он не делает ничего неопределенно полезного.
Если у вас включены предупреждения об ошибках, он не будет собираться, по крайней мере, в VS2005
@Chris: вы можете исправить это, добавив goto http; ;-)
К сожалению, goto http; на самом деле не следует по URL. :(
@titaniumdecoy Подождите, Java поддерживает goto?
@muntoo, вообще-то да, вроде того. stackoverflow.com/a/2545160/65977
Указатель арифметики.
На самом деле это функция C, но я заметил, что немногие люди, использующие C / C++, действительно знают, что она вообще существует. Я считаю, что эта особенность языка C действительно свидетельствует о гениальности и видении его изобретателя.
Короче говоря, арифметика указателей позволяет компилятору выполнять [n] как * (a + n) для любого типа a. В качестве примечания, поскольку '+' коммутативен, a [n], конечно, эквивалентно n [a].
Избавляемся от форвардных объявлений:
struct global
{
void main()
{
a = 1;
b();
}
int a;
void b(){}
}
singleton;
Написание операторов switch с помощью операторов?::
string result =
a==0 ? "zero" :
a==1 ? "one" :
a==2 ? "two" :
0;
Делаем все в одной строке:
void a();
int b();
float c = (a(),b(),1.0f);
Обнуление структур без memset:
FStruct s = {0};
Нормализация / обертывание значений угла и времени:
int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150
Назначение референсов:
struct ref
{
int& r;
ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;
FStruct s = {}; еще короче.
В последнем примере было бы проще: a (); б (); float c = 1.0f;
@Zifre: фокус как раз в последней строке float c = (a (), b (), 1.0f); - помещать все, что закрыто полуклоном, в одну строку - это вообще не уловка. Но будьте осторожны: int d = (a (), b (), 1.0f); получит значение b!
Этот синтаксис «float c = (a (), b (), 1.0f);» полезен для акцентирования ассигнования-операции (присвоение «с»). Операции присваивания важны в программировании, потому что они с меньшей вероятностью станут устаревшими IMO. Не знаю почему, это может быть связано с функциональным программированием, когда состояние программы переназначается каждый кадр. PS. И нет, «int d = (11,22,1.0f)» будет равно «1». Протестировано минуту назад с VS2008.
+1 Разве вы не должны быть вызовmain? Я бы предложил global().main(); и просто забыл о синглтоне (вы можете просто работать с временным, что продлевает срок его службы)
Я сомневаюсь, что присвоение ссылок переносимо. Тем не менее, мне нравится, что структура отклоняет предварительные объявления.
Приятной особенностью, которая используется нечасто, является блок try-catch для всей функции:
int Function()
try
{
// do something here
return 42;
}
catch(...)
{
return -1;
}
Основное использование будет заключаться в преобразовании исключения в другой класс исключения и повторном вызове или в преобразовании между исключениями и обработкой кода ошибки на основе возврата.
Я не думаю, что вы можете return из блока catch функции Try, только перебросить.
Я просто попытался скомпилировать вышеуказанное, и это не дало никаких предупреждений. Я думаю, что приведенный выше пример работает.
Возврат запрещен только для строителей. Блок try функции конструктора будет отлавливать ошибки при инициализации базы и членов (единственный случай, когда блок try функции делает что-то иное, чем просто попытка внутри функции); отказ от повторного броска приведет к получению неполного объекта.
Да. Это очень полезно. Я написал макросы BEGIN_COM_METHOD и END_COM_METHOD для перехвата исключений и возврата HRESULTS, чтобы исключения не просачивались из класса, реализующего COM-интерфейс. Это сработало.
@puetzk Я полагаю, было бы очень полезно вернуть "DefaultObject" из конструктора, который потерпел неудачу. Как если бы конструктор вышел из строя. Вы все еще можете вернуть пустой объект. Как будто вы хотите загрузить изображение. При ошибке загрузки возвращается изображение по умолчанию.
Это довольно круто. Я понятия не имел, что вы можете это сделать, и людям не кажется, что он даже компилируется.
Как указывает @puetzk, это единственный способ обрабатывать исключения, создаваемые что-либо в список инициализаторов конструктора, такие как конструкторы базовых классов или конструкторы членов данных.
@kizzx: это экономит вам одну открытую и одну закрывающую скобу.
@kizzx: он также сохраняет дальнейшие отступы тела функции ... хорошо, если вам лень менять отступ или работать на экране фиксированной ширины и вы хотите использовать больше.
Скрытые возможности:
Если функция генерирует исключение, не указанное в ее спецификациях исключения, но функция имеет std::bad_exception в своей спецификации исключения, исключение преобразуется в std::bad_exception и генерируется автоматически. Таким образом вы хотя бы узнаете, что был брошен bad_exception. Узнать больше здесь.
функциональные блоки try
Ключевое слово template для устранения неоднозначности определений типов в шаблоне класса. Если имя специализации шаблона элемента появляется после оператора ., -> или ::, и это имя имеет явно определенные параметры шаблона, добавьте к имени шаблона элемента префикс ключевого слова template. Узнать больше здесь.
Значения по умолчанию для параметров функции могут быть изменены во время выполнения. Узнать больше здесь.
A[i] работает так же хорошо, как i[A]
Временные экземпляры класса можно изменять! Неконстантная функция-член может быть вызвана для временного объекта. Например:
struct Bar {
void modify() {}
}
int main (void) {
Bar().modify(); /* non-const function invoked on a temporary. */
}
Узнать больше здесь.
Если два разных типа присутствуют до и после : в тернарном (?:) операторном выражении, то результирующий тип выражения является наиболее общим из двух. Например:
void foo (int) {}
void foo (double) {}
struct X {
X (double d = 0.0) {}
};
void foo (X) {}
int main(void) {
int i = 1;
foo(i ? 0 : 0.0); // calls foo(double)
X x;
foo(i ? 0.0 : x); // calls foo(X)
}
P Папа: A [i] == * (A + i) == * (i + A) == i [A]
Я получаю коммутацию, просто это означает, что [] не имеет собственного семантического значения и просто эквивалентен замене в макро-стиле, где «x [y]» заменяется на «(* ((x) + (y ))) ". Совсем не то, что я ожидал. Интересно, почему это так определяется.
Обратная совместимость с C
Что касается вашего первого пункта: есть один частный случай, когда вы иметь реализуете чистую виртуальную функцию: чистые виртуальные деструкторы.
Инициализация массива в конструкторе.
Например, в классе, если у нас есть массив int как:
class clName
{
clName();
int a[10];
};
Мы можем инициализировать все элементы в массиве по умолчанию (здесь все элементы массива равны нулю) в конструкторе как:
clName::clName() : a()
{
}
Вы можете сделать это с любым массивом где угодно.
@Potatoswatter: сложнее, чем кажется, из-за самого неприятного синтаксического анализа. Я не могу придумать, где еще это можно было бы сделать, кроме может быть возвращаемого значения
Если тип массива является типом класса, тогда это не нужно, верно?
map::operator[] создает запись, если ключ отсутствует, и возвращает ссылку на созданное по умолчанию значение записи. Итак, вы можете написать:
map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
s.assign(...);
}
cout << s;
Я поражен тем, сколько программистов на C++ этого не знают.
С другой стороны, вы не можете использовать operator [] на карте const.
+1 за Ника, люди могут сойти с ума, если не знают о .find().
или «const map::operator[] выдает сообщения об ошибках»
Это не особенность языка, это особенность стандартной библиотеки шаблонов. Это также довольно очевидно, поскольку operator [] возвращает действительную ссылку.
Он по-прежнему кусает людей: «map <int, data *> m -> if (m [i] == 0) return; / * Not found * /» - коварная утечка памяти.
Мне пришлось какое-то время использовать карты на C#, где карты не ведут себя таким образом, чтобы понять, что это особенность. Я думал, что меня это раздражает больше, чем я им пользовался, но, похоже, я ошибался. Мне не хватает этого на C#.
Когда-то, еще не зная много о контейнерах C++, я уже много использовал ассоциативные массивы PHP. На этом фоне это было совершенно противоположным сюрпризом. На самом деле для меня было довольно удивительно, что нет V operator[] (K) const.
Помещение функций или переменных в безымянное пространство имен осуждает использование static, чтобы ограничить их областью файлов.
"осуждает" - сильный термин ...
@Potato: Старый комментарий, я знаю, но в стандарте говорится, что использование static в области пространства имен устарело, с предпочтением для безымянных пространств имен.
@GMan: нет проблем, я не думаю, что ТАК страницы действительно "умирают". По обе стороны истории, static в глобальном масштабе никоим образом не является устаревшим. (Для справки: C++ 03 §D.2)
А, при более внимательном чтении: «Имя, объявленное в глобальном пространстве имен, имеет глобальную область действия (также называемую глобальной областью)». Это действительно так?
@ Картофель: Ага. :) static следует использовать только внутри класса или функции.
Большинство программистов на C++ знакомы с тернарным оператором:
x = (y < 0) ? 10 : 20;
Однако они не понимают, что его можно использовать как lvalue:
(a == 0 ? a : b) = 1;
что является сокращением для
if (a == 0)
a = 1;
else
b = 1;
Используйте с осторожностью :-)
Очень интересно. Я вижу, что это создает нечитаемый код.
Вы должны переместить закрывающую скобку: «(a == 0? A: b) = 1», иначе она компилируется как «a == 0? A: (b = 1)». Обратите внимание, что скобки вокруг "a == 0" не обязательны.
Ой. (а == 0? а: б) = (у <0? 10: 20);
Странно, что этого нет в C. Такие вещи, как однострочные комментарии (//), были перенесены обратно, и это определенно не могло сломать какой-либо код. Думаю, это тоже может быть полезно. Ну что ж.
(b? trueCount: falseCount) ++
Допускает ли это какой-либо другой язык?
Обычно я воздерживаюсь от использования тернарного оператора, потому что он затрудняет чтение кода.
Не знаю, специфичен ли он для GCC, но я был удивлен, обнаружив, что это тоже сработало: (value ? function1 : function2)().
@Chris Burt-Brown: Нет, это должно работать везде, если они имеют один и тот же тип (т.е. нет аргументов по умолчанию). function1 и function2 неявно преобразуются в указатели функций, а результат неявно преобразуется обратно.
Считываем файл в вектор строк:
vector<string> V;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(V));
Или: vector <string> V ((istream_iterator <string> (cin)), istream_iterator <string>);
вы имеете в виду vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>()); - отсутствие скобок после второго параметра
На самом деле это не скрытая функция C++. Больше функции STL. STL! = Язык
Pointer arithmetics.
Программисты на C++ предпочитают избегать указателей из-за возможных ошибок.
Но самый крутой C++, который я когда-либо видел? Аналоговые литералы.
Избегаем указателей из-за ошибок? По сути, указатели - это все, что связано с динамическим кодированием на C++!
Аналоговые литералы отлично подходят для запутанных записей конкурса C++, особенно типа ASCII-art.
Довольно скрытая особенность заключается в том, что вы можете определять переменные внутри условия if, и его область действия будет охватывать только if и его блоки else:
if (int * p = getPointer()) {
// do something
}
Некоторые макросы используют это, например, чтобы обеспечить некоторую "заблокированную" область видимости, например:
struct MutexLocker {
MutexLocker(Mutex&);
~MutexLocker();
operator bool() const { return false; }
private:
Mutex &m;
};
#define locked(mutex) if (MutexLocker const& lock = MutexLocker(mutex)) {} else
void someCriticalPath() {
locked(myLocker) { /* ... */ }
}
Также BOOST_FOREACH использует его под капотом. Чтобы завершить это, это возможно не только в if, но и в переключателе:
switch(int value = getIt()) {
// ...
}
и в цикле while:
while(SomeThing t = getSomeThing()) {
// ...
}
(а также в состоянии for). Но я не уверен, насколько они полезны :)
Аккуратный! Я никогда не знал, что вы сможете это сделать ... это избавило бы (и сэкономит) некоторые хлопоты при написании кода с возвращаемыми значениями ошибок. Есть ли способ сохранить в этой форме условное выражение вместо! = 0? if ((int r = func ()) <0) похоже не работает ...
puetzk, нет, нет. но рад, что тебе понравилось :)
На самом деле моему другу пришлось использовать эту первую функцию, чтобы точно создать очень сложный макрос, который был необходим в существующей системе.
+1 Наслаждайтесь этой ссылкой: ddj.com/cpp/184401728
На самом деле это скрыто только для людей, которые никогда серьезно не писали код C. Обычно очень фиксирует возвращаемые значения в таких переменных.
@Frerich, это вообще невозможно в коде C. Я думаю, вы думаете о if ((a = f()) == b) ..., но этот ответ фактически объявляет переменную в условии.
Это не сильно отличается от for (int i=foo(); i<N; ++i). Срок службы i - до выхода из цикла. Итератор очень часто определяется внутри скобок.
@Angry это совсем другое, потому что объявление переменной сразу проверяется на его логическое значение. Также есть сопоставление с циклами for, которое выглядит как for(...; int i = foo(); ) ...;. Оно будет проходить через тело, пока i истинно, инициализируя его каждый раз заново. Цикл, который вы показываете, просто демонстрирует объявление переменной, но не объявление переменной, которое одновременно действует как условие :)
Очень хорошо, за исключением того, что вы не упомянули, что предполагаемое использование этой функции было для динамического приведения указателей, я считаю.
От Истины C++.
Определение функций с идентичными подписями в одной и той же области, поэтому это допустимо:
template<class T> // (a) a base template
void f(T) {
std::cout << "f(T)\n";
}
template<>
void f<>(int*) { // (b) an explicit specialization
std::cout << "f(int *) specilization\n";
}
template<class T> // (c) another, overloads (a)
void f(T*) {
std::cout << "f(T *)\n";
}
template<>
void f<>(int*) { // (d) another identical explicit specialization
std::cout << "f(int *) another specilization\n";
}
+1 для неясности, хотя вы сами скрываете вещи, опуская тот факт, что приведенный выше код требует еще 2 деклараций шаблонов функций (1 в начале, 1 между ними) для компиляции.
Если оператор delete () принимает аргумент размера в дополнение к * void, это означает, что он будет, в значительной степени, базовым классом. Этот аргумент размера делает возможной проверку размера типов, чтобы уничтожить правильный. Вот что об этом сообщает Стивен Дьюхерст:
Notice also that we've employed a two-argument version of operator delete rather than the usual one-argument version. This two-argument version is another "usual" version of member operator delete often employed by base classes that expect derived classes to inherit their operator delete implementation. The second argument will contain the size of the object being deleted—information that is often useful in implementing custom memory management.
Одна из самых интересных грамматик любого языка программирования.
Три из этих вещей принадлежат друг другу, а две совершенно разные ...
SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));
Все, кроме третьего и пятого, определяют объект SomeType в стеке и инициализируют его (с помощью u в первых двух случаях и конструктором по умолчанию в четвертом. Третий объявляет функцию, которая не принимает параметров и возвращает SomeType. Пятый аналогичным образом объявляет функцию, которая принимает один параметр по значению типа SomeType с именем u.
есть ли разница между 1-м и 2-м? хотя я знаю, что это обе инициализации.
Comptrol: Я так не думаю. Оба в конечном итоге вызовут конструктор копирования, хотя первый ВЫГЛЯДИТ как оператор присваивания, на самом деле это конструктор копирования.
Если u является типом, отличным от SomeType, то первый вызовет сначала конструктор преобразования, а затем конструктор копирования, тогда как второй вызовет только конструктор преобразования.
1-й - неявный вызов конструктора, 2-й - явный вызов. Посмотрите на следующий код, чтобы увидеть разницу: #include <iostream> class sss {public: explicit sss (int) {std :: cout << "int" << std :: endl; }; sss (двойной) {std :: cout << "double" << std :: endl; }; }; int main () {sss ddd (7); // вызывает конструктор int sss xxx = 7; // вызывает двойной конструктор return 0; }
Верно - первая строка не будет работать, если конструктор объявлен явно.
Иначе известный как: Самый неприятный синтаксический анализ. «Исправлено» в C++ 0x с новым синтаксисом: SomeType t {SomeType {u}}
Мало что известно о том, что союзы тоже могут быть шаблонами:
template<typename From, typename To>
union union_cast {
From from;
To to;
union_cast(From from)
:from(from) { }
To getTo() const { return to; }
};
И у них тоже могут быть конструкторы и функции-члены. Ничего общего с наследованием (включая виртуальные функции).
Интересно! Итак, вы должны инициализировать всех участников? Соответствует ли он обычному порядку структуры, подразумевая, что последний член будет инициализирован «поверх» более ранних членов?
j_random_hacker ах, да это чушь. хороший улов. Я написал это как структуру. подожди, я исправлю это
Разве это не вызывает неопределенное поведение?
@gbacon, да, он вызывает неопределенное поведение, если From и To установлены и используются соответственно. Однако такое объединение можно использовать с определенным поведением (при этом To является массивом символов без знака или структурой, разделяющей начальную последовательность с From). Даже если вы используете его неопределенным образом, он все равно может быть полезен для низкоуровневой работы. В любом случае, это всего лишь один пример шаблона объединения - шаблонное объединение может быть использовано и в других целях.
Это интересно, никогда не знал, что у объединений на самом деле есть эти функции из классов и структур!
Осторожнее с конструктором. Обратите внимание, что вам необходимо создать только первый элемент, и это разрешено только в C++ 0x. В соответствии с текущим стандартом вы должны придерживаться тривиально конструируемых типов. И никаких деструкторов.
Так в основном вы это делаете? *(B *)&a
@ JohannesSchaub-litb: также можно создавать шаблоны битовых полей: stackoverflow.com/a/8309592/85371
Особого внимания требует определение обычных функций друзей в шаблонах классов:
template <typename T>
class Creator {
friend void appear() { // a new function ::appear(), but it doesn't
… // exist until Creator is instantiated
}
};
Creator<void> miracle; // ::appear() is created at this point
Creator<double> oops; // ERROR: ::appear() is created a second time!
В этом примере два разных экземпляра создают два идентичных определения - прямое нарушение ODR
Поэтому мы должны убедиться, что параметры шаблона шаблона класса присутствуют в типе любой дружественной функции, определенной в этом шаблоне (если мы не хотим предотвратить создание более одного экземпляра шаблона класса в конкретном файле, но это маловероятно). Давайте применим это к варианту нашего предыдущего примера:
template <typename T>
class Creator {
friend void feed(Creator<T>*){ // every T generates a different
… // function ::feed()
}
};
Creator<void> one; // generates ::feed(Creator<void>*)
Creator<double> two; // generates ::feed(Creator<double>*)
Отказ от ответственности: я вставил этот раздел из Шаблоны C++: полное руководство / Раздел 8.4
Опасный секрет
Fred* f = new(ram) Fred(); http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10
f->~Fred();
Мой любимый секрет, который я редко использую:
class A
{
};
struct B
{
A a;
operator A&() { return a; }
};
void func(A a) { }
int main()
{
A a, c;
B b;
a=c;
func(b); //yeah baby
a=b; //gotta love this
}
Умм, неприятно это говорить, но на самом деле это не очень скрытый «оператор приведения типов». Об этом, наверное, знает любой, кто хоть раз видел перегрузки операторов.
Ведущие программисты на моей работе не знали об этом. Только один человек, которого я встречал, знал об этом. Также я видел учебники, в которых это не упоминается, включая мой любимый учебник cplusplus.com/doc/tutorial
class Empty {};
namespace std {
// #1 specializing from std namespace is okay under certain circumstances
template<>
void swap<Empty>(Empty&, Empty&) {}
}
/* #2 The following function has no arguments.
There is no 'unknown argument list' as we do
in C.
*/
void my_function() {
cout << "whoa! an error\n"; // #3 using can be scoped, as it is in main below
// and this doesn't affect things outside of that scope
}
int main() {
using namespace std; /* #4 you can use using in function scopes */
cout << sizeof(Empty) << "\n"; /* #5 sizeof(Empty) is never 0 */
/* #6 falling off of main without an explicit return means "return 0;" */
}
Нет, расширение std абсолютно нормально для нет, и стандарт явно запрещает это (за одним исключением: перегрузки swap).
Спасибо. Я ошибся. Но это просто своп или стандарт говорит: только специализации стандартных шаблонов?
Допускается специализация шаблонов в std, если специализация зависит от типа, определенного пользователем. Обмен не ограничен.
Однако ваш конкретный пример недействителен, потому что ваша специализация не соответствует ни одной сигнатуре шаблона функции. У вас должно быть два эталонных параметра и т. д. :)
«Для программы на C++ не определено добавлять объявления или определения в пространство имен std или пространства имен в пространстве имен std, если не указано иное». (17.4.3.1/1) Вы не можете перегрузить std :: swap и т. д., Но можете специализировать их: «Программа может добавлять специализации шаблона для любого стандартного шаблона библиотеки в пространство имен std. Такая специализация ... приводит к undefined, если только объявление не зависит от пользовательского имени внешней связи и если специализация не соответствует требованиям стандартной библиотеки для исходного шаблона ". (17.4.3.1/1)
Обратите внимание, что std :: swap, в частности, не может быть частично специализированным (это функция) и не может быть перегружен (см. Стандартную цитату выше), поэтому вы должны сделать что-то еще для шаблонов, которые вы пишете. Пример использования ADL с std :: swap в stackoverflow.com/questions/2197141/….
@Konrad: Где указано это исключение? Я не верю, что он существует.
@ Роджер, ты прав. Я имел в виду специализацию, а не перегрузку.
Идиома косвенного преобразования:
Suppose you're designing a smart pointer class. In addition to overloading the operators * and ->, a smart pointer class usually defines a conversion operator to bool:
template <class T>
class Ptr
{
public:
operator bool() const
{
return (rawptr ? true: false);
}
//..more stuff
private:
T * rawptr;
};
The conversion to bool enables clients to use smart pointers in expressions that require bool operands:
Ptr<int> ptr(new int);
if (ptr ) //calls operator bool()
cout<<"int value is: "<<*ptr <<endl;
else
cout<<"empty"<<endl;
Furthermore, the implicit conversion to bool is required in conditional declarations such as:
if (shared_ptr<X> px = dynamic_pointer_cast<X>(py))
{
//we get here only of px isn't empty
}
Alas, this automatic conversion opens the gate to unwelcome surprises:
Ptr <int> p1;
Ptr <double> p2;
//surprise #1
cout<<"p1 + p2 = "<< p1+p2 <<endl;
//prints 0, 1, or 2, although there isn't an overloaded operator+()
Ptr <File> pf;
Ptr <Query> pq; // Query and File are unrelated
//surprise #2
if (pf==pq) //compares bool values, not pointers!
Решение: используйте идиому «косвенное преобразование», преобразовав указатель в член данных [pMember] в bool, чтобы было только одно неявное преобразование, которое предотвратит вышеупомянутое неожиданное поведение: pMember-> bool, а не bool-> something еще.
Обратите внимание на разницу между инициализацией указателя на бесплатную функцию и указателя на функцию-член:
функция-член:
struct S
{
void func(){};
};
int main(){
void (S::*pmf)()=&S::func;// & is mandatory
}
и бесплатная функция:
void func(int){}
int main(){
void (*pf)(int)=func; // & is unnecessary it can be &func as well;
}
Благодаря этому избыточному & вы можете добавлять манипуляторы потоков, которые являются бесплатными функциями, в цепочку без него:
cout<<hex<<56; //otherwise you would have to write cout<<&hex<<56, not neat.
Эмуляция переосмыслить актерский состав с помощью статический бросок:
int var;
string *str = reinterpret_cast<string*>(&var);
приведенный выше код эквивалентен следующему:
int var;
string *str = static_cast<string*>(static_cast<void*>(&var));
Какой в этом смысл?
Зифре, ничего. но результат в обоих случаях один и тот же. см. здесь: informit.com/blogs/…
Я действительно не вижу в этом "хитрости", поскольку ваш второй пример в основном просто описывает то, что происходит с reinterpret_cast. Вы также могли сделать это: string str = (строка) (void *) (& var); Кроме того, разве ваш static_cast <void> не должен быть static_cast <void *>?
@Jared, никакого прикола. Я просто не знал, как работает reinterpret_cast, и хотел им поделиться. Вам даже не нужно добавлять (void *), ведь кастинг в стиле C включает как reinterpret_cast, так и static_cast. Вы правы насчет void *, спасибо.
Это неправда. Двойное статическое приведение - это то, что обычно делает считать reinterpret_cast. Но это не так. Они не эквивалентны. static_cast гарантирует, что он даст указатель на тот же адрес. reinterpret_cast просто предоставляет отображение, определяемое реализацией.
static_cast гарантирует только тот же адрес, если вы вернетесь к тому же типу, который был у вас до преобразования в void*. В Стандарте есть некоторая скрытая тайна о некоторой особой обработке двойного static_cast, как показано в этом ответе, но мне еще нужно увидеть, чтобы кто-нибудь доказал реальное существование этой специальной обработки помимо формулировок «есть это, но я не знаю, где».
Класс и структура class-keys почти идентичны. Основное отличие состоит в том, что классы по умолчанию имеют частный доступ для членов и баз, а структуры по умолчанию - общедоступные:
// this is completely valid C++:
class A;
struct A { virtual ~A() = 0; };
class B : public A { public: virtual ~B(); };
// means the exact same as:
struct A;
class A { public: virtual ~A() = 0; };
struct B : A { virtual ~B(); };
// you can't even tell the difference from other code whether 'struct'
// or 'class' was used for A and B
Объединения также могут иметь члены и методы и по умолчанию имеют общий доступ, как и структуры.
В части инициализации цикла for можно объявлять не только переменные, но также классы и функции.
for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
...
}
Это позволяет использовать несколько переменных разных типов.
Приятно осознавать, что вы можете это сделать, но лично я бы очень старался избегать подобных действий. В основном потому, что его трудно читать.
@Sir: Тем не менее, это может быть интересно использовать для сгенерированного кода или макрокоманды.
-1: Извините, но, к сожалению, это не работает на VS2008. for (struct {int a; float b;} цикл = {1, 2} ;;); причины: "ошибка C2332: 'struct': отсутствует имя тега"
На самом деле, в этом контексте будет работать пара: for (std :: pair <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
@Valentin, тогда я рекомендую вам попробовать сделать отчет об ошибке VS2008 вместо того, чтобы голосовать против скрытой функции. Очевидно, это вина вашего компилятора.
Хм, в msvc10 тоже не работает. Как грустно :(
@avakar на самом деле, gcc представил ошибку, из-за которой он тоже отклоняется, в v4.6 :) см. gcc.gnu.org/bugzilla/show_bug.cgi?id=46791
Напоминает мне о «напишите программу на C, чтобы выводить свое имя, но не используйте точки с запятой в вашей программе». Мы просто используем если (printf ("Nav"))
Добавление ограничения в шаблоны.
У примитивных типов есть конструкторы.
int i(3);
работает.
Это не конструктор, это просто форма инициализации, а именно прямая инициализация. Примитивные типы не имеют конструкторов.
@Midas: он инициализировал i значением 3. Этот синтаксис обеспечивает единообразие с пользовательскими типами, что особенно полезно в шаблонах.
Мне кажется, что о безымянных пространствах имен мало кто знает:
namespace {
// Classes, functions, and objects here.
}
Безымянные пространства имен ведут себя так, как если бы они были заменены:
namespace __unique_name__ { /* empty body */ }
using namespace __unique_name__;
namespace __unique_name__ {
// original namespace body
}
«.. где все вхождения [этого уникального имени] в единице перевода заменены одним и тем же идентификатором, и этот идентификатор отличается от всех других идентификаторов во всей программе». [C++ 03, 7.3.1.1/1]
К сожалению, использование одного только пространства имен anon по-прежнему оставляет внешние (хотя и не имеющие ссылки) символы в вашей последней ссылке. Пока это не изменится, если вы заботитесь о конечном двоичном размере, лучше всего объявить любые локальные функции файла и статические переменные.
@Jon: А какая разница после удаления исполняемых файлов?
Вы можете получить доступ к защищенным данным и функциям любого класса без неопределенного поведения и с ожидаемой семантикой. Читайте дальше, чтобы узнать, как это сделать. Прочтите также об этом отчет о дефектах.
Обычно C++ запрещает вам доступ к нестатическим защищенным членам объекта класса, даже если этот класс является вашим базовым классом.
struct A {
protected:
int a;
};
struct B : A {
// error: can't access protected member
static int get(A &x) { return x.a; }
};
struct C : A { };
Это запрещено: вы и компилятор не знаете, на что на самом деле указывает ссылка. Это может быть объект C, и в этом случае класс B не имеет никакого отношения к своим данным. Такой доступ предоставляется только в том случае, если x является ссылкой на производный класс или производный от него. И он может позволить произвольному фрагменту кода читать любой защищенный член, просто создавая «одноразовый» класс, который считывает члены, например std::stack:
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
static std::deque<int> &get(std::stack<int> &s) {
// error: stack<int>::c is protected
return s.c;
}
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = pillager::get(s);
}
Конечно, как видите, это нанесет слишком большой ущерб. Но теперь указатели на элементы позволяют обойти эту защиту! Ключевым моментом является то, что тип указателя члена привязан к классу, который фактически содержит упомянутый член - нет к классу, который вы указали при получении адреса. Это позволяет нам обойти проверку
struct A {
protected:
int a;
};
struct B : A {
// valid: *can* access protected member
static int get(A &x) { return x.*(&B::a); }
};
struct C : A { };
И, конечно же, это также работает с примером std::stack.
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
static std::deque<int> &get(std::stack<int> &s) {
return s.*(pillager::c);
}
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = pillager::get(s);
}
Это будет еще проще с объявлением using в производном классе, которое делает имя члена общедоступным и ссылается на член базового класса.
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
using std::stack<int>::c;
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = s.*(&pillager::c);
}
Указатели на элементы и оператор указателя на член -> *
#include <stdio.h>
struct A { int d; int e() { return d; } };
int main() {
A* a = new A();
a->d = 8;
printf("%d %d\n", a ->* &A::d, (a ->* &A::e)() );
return 0;
}
Для методов (a -> * & A :: e) () немного похож на Function.call () из javascript
var f = A.e
f.call(a)
Для участников это немного похоже на доступ с помощью оператора []
a['d']
Многие знают о метафункции identity / id, но есть хороший вариант использования для случаев, не связанных с шаблоном: Простота написания объявлений:
// void (*f)(); // same
id<void()>::type *f;
// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);
// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];
// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;
Это очень помогает при расшифровке объявлений C++!
// boost::identity is pretty much the same
template<typename T>
struct id { typedef T type; };
Интересно, но изначально у меня были проблемы с более при чтении некоторых из этих определений. Другой способ решить проблему с декларациями C++ - написать несколько псевдонимов типов шаблонов: template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T; -> pointer<function<void,int>> f(pointer<function<void,void>>);, pointer<void(int)> f(pointer<void()>); или function<pointer<function<void,int>>,pointer<function<void,void>>> f;.
Я считаю, что создание рекурсивных шаблонов довольно круто:
template<class int>
class foo;
template
class foo<0> {
int* get<0>() { return array; }
int* array;
};
template<class int>
class foo<i> : public foo<i-1> {
int* get<i>() { return array + 1; }
};
Я использовал это для создания класса с 10-15 функциями, которые возвращают указатели на различные части массива, поскольку используемый мной API требовал одного указателя на функцию для каждого значения.
Т.е. программирование компилятора для генерации набора функций с помощью рекурсии. Проще простого. :)
main () не требует возвращаемого значения:
int main(){}
это самая короткая действующая программа на C++.
Технически нет, потому что вам нужна новая строка, чтобы она была действительной: p
@Kaz: перевод строки есть, нажмите «изменить», чтобы просмотреть исходный код. :)
Вы можете использовать шаблоны битовых полей.
template <size_t X, size_t Y>
struct bitfield
{
char left : X;
char right : Y;
};
Я еще не придумал для этого какой-либо цели, но это, черт возьми, меня удивило.
Смотрите здесь, где я недавно предложил это для n-битной арифметики: stackoverflow.com/questions/8309538/…
Мне больше всего нравится (на данный момент) отсутствие сематики в таких утверждениях, как А = В = С. Какое значение A в основном не определено.
Подумайте об этом:
class clC
{
public:
clC& operator=(const clC& other)
{
//do some assignment stuff
return copy(other);
}
virtual clC& copy(const clC& other);
}
class clB : public clC
{
public:
clB() : m_copy()
{
}
clC& copy(const clC& other)
{
return m_copy;
}
private:
class clInnerB : public clC
{
}
clInnerB m_copy;
}
теперь A может иметь тип, недоступный для каких-либо объектов, кроме объектов типа clB, и иметь значение, не связанное с C.
да - вы в основном говорите, что вы можете использовать перегрузку для изменения семантики? Теперь, если бы вы продемонстрировали, что семантика a=b=c не была исправлена даже для встроенных типов / операторов ... Это было бы интересно. § 3.10Например, встроенные операторы присваивания ожидают, что левый операнд является lvalue, а правый операнд - prvalue, и в результате выдают lvalue. Определяемые пользователем операторы - это функции, а категории ожидаемых и получаемых ими значений определяются их параметрами и типами возвращаемых значений.
@sehe, как насчет перегрузки для int, чтобы всегда назначать, скажем, 1/3 c для a в a = b = c?
как вы определите нестатический членoperator= на учебный класс (sic) int?
@ он никогда не говорил, что я буду
how about an overload for int to always assign say 1/3 of c to a in a=b=c? для меня не материализуется в юридический кодекс. Не могли бы вы показать, что это у вас за иметь в виду? Вы заявляете lack of semantics как скрытая особенность языка C++. Думаю, имеет смысл объяснить, что вы имели в виду?
@sehe, вы утверждаете, что семантика оператора присваивания является исправлена в С ++? что вы можете обосновать (с уверенностью), что a будет назначено в связи с b и c в утверждении a = b = c?
Ой. Тогда это неприятно. Я думал, вы каким-то образом предполагаете, что это «недооценка» и скрытая функция. Очевидно, это задумано. Без этого понятия не было бы такой вещи, как шаблоны выражений, поэтому не было бы Blitz ++, Eigen, Boost Proto (черт возьми, 25% ускорения) и т. д. И так далее, не могли бы вы тогда сказать то же самое о C#? ideone.com/6dxZG
Семантика фиксируются для встроенных типов. Вы также можете рассуждать об этом для нестандартных типов, жестко и формально. (Если operator= принимает const&, значение не будет затираться.). Ваше вступительное предложение слишком широкое: the lack of sematics in a statement like A=B=C. Не просто любое утверждение: утверждение, включающее типы с нестандартной семантикой. Ну что? И это не недостаток семантики, а изобилие возможностей. (Первое предполагает языковой недостаток)
то, что любой данный оператор может означать любую данную вещь, по определению является отсутствием семантики. Да, это может создать изобилие возможностей, но недостаток средств - это недостаток смысла
Малоизвестно, но следующий код подойдет
void f() { }
void g() { return f(); }
А также следующий странно выглядящий
void f() { return (void)"i'm discarded"; }
Зная об этом, вы можете воспользоваться преимуществами в некоторых сферах. Один пример: функции void не могут возвращать значение, но вы также можете не просто ничего не возвращать, потому что они могут быть созданы с непустым. Вместо сохранения значения в локальной переменной, что вызовет ошибку для void, просто верните значение напрямую.
template<typename T>
struct sample {
// assume f<T> may return void
T dosomething() { return f<T>(); }
// better than T t = f<T>(); /* ... */ return t; !
};
Иногда вы правильно используете оператор запятой, но вы хотите, чтобы ни один пользовательский оператор запятой не мешал, потому что, например, вы полагаетесь на точки последовательности между левой и правой стороной или хотите убедиться, что ничто не мешает желаемому действие. Вот где в игру вступает void():
for(T i, j; can_continue(i, j); ++i, void(), ++j)
do_code(i, j);
Не обращайте внимания на заполнители, которые я поставил для условия и кода. Что важно, так это void(), который заставляет компилятор использовать встроенный оператор запятой. Иногда это может быть полезно при реализации классов признаков.
Я просто использовал это, чтобы прикончить свой игнорировать выражение излишнего выражения. :)
Вы можете просмотреть все предопределенные макросы с помощью переключателей командной строки с некоторыми компиляторами. Это работает с gcc и icc (компилятор Intel C++):
$ touch empty.cpp
$ g++ -E -dM empty.cpp | sort >gxx-macros.txt
$ icc -E -dM empty.cpp | sort >icx-macros.txt
$ touch empty.c
$ gcc -E -dM empty.c | sort >gcc-macros.txt
$ icc -E -dM empty.c | sort >icc-macros.txt
Для MSVC они перечислены в единственное место. Они могут быть задокументированы в одном месте и для других, но с помощью приведенных выше команд вы можете четко определить видеть, что определено, а что нет, и какие именно значения используются, после применения всех других параметров командной строки.
Сравните (после сортировки):
$ diff gxx-macros.txt icx-macros.txt
$ diff gxx-macros.txt gcc-macros.txt
$ diff icx-macros.txt icc-macros.txt
Тернарный условный оператор ?: требует, чтобы его второй и третий операнды имели "приемлемые" типы (говоря неформально). Но это требование имеет одно исключение (каламбур): либо второй, либо третий операнд может быть выражением throw (имеющим тип void), независимо от типа другого операнда.
Другими словами, с помощью оператора ?: можно написать следующие правильно допустимые выражения C++.
i = a > b ? a : throw something();
Кстати, тот факт, что выражение throw на самом деле является выражение (типа void), а не оператором, является еще одной малоизвестной особенностью языка C++. Это, помимо прочего, означает, что следующий код абсолютно верен.
void foo()
{
return throw something();
}
хотя в этом нет особого смысла (возможно, в каком-то универсальном коде шаблона это может пригодиться).
Как бы то ни было, у Нила есть вопрос по этому поводу: stackoverflow.com/questions/1212978/…, просто для дополнительной информации.
map::insert(std::pair(key, value)); не перезаписывает, если значение ключа уже существует.
Вы можете создать экземпляр класса сразу после его определения: (Я мог бы добавить, что эта функция дала мне сотни ошибок компиляции из-за отсутствия точки с запятой, и я никогда не видел, чтобы кто-то использовал ее в классах)
class MyClass {public: /* code */} myClass;
Я знаю человека, который одновременно определяет геттер и сеттер только одним методом. Так:
class foo
{
int x;
int* GetX(){
return &x;
}
}
Теперь вы можете использовать это как получатель, как обычно (ну, почти):
int a = *GetX();
и как установщик:
*GetX() = 17;
Хотя я бы сменил имя. Это приводит к большой путанице.
Он использует его только тогда, когда программирует что-то для себя.
Зачем ему в таком случае возвращать int* вместо int& !?
Без обид, но мой голос против вашего друга. :) Нет причин возвращать указатель, если подойдет неконстантная ссылка. Кроме того, нет смысла даже иметь геттер, это просто пустая трата места; сделайте переменную-член общедоступной.
На самом деле это не пустая трата места, поскольку он объявлен встроенным;)
Это лишает смысла использование геттеров и сеттеров, открывая внутреннюю реализацию, тем самым делая невозможным изменение реализации позже, не затрагивая каждого вызывающего. Вашему другу удалось совместить все недостатки прямого доступа к участникам со всеми недостатками геттеров / сеттеров. Хорошая работа.
@Ferruccio: и вы забыли упомянуть о проблемах времени жизни из-за возвращенных ссылок / указателей на участников .. Действительно ужасно плохой код.
Метапрограммирование шаблона есть.
На самом деле не скрытая функция, а чистая классность:
#define private public
Это даже не функция, это просто незаконно переопределять ключевые слова.
Говорит где? Этап предварительной обработки применяется еще до того, как ключевые слова будут учтены анализатором / лексером. Вы можете переопределить практически ВСЕ.
Чтобы доказать это, попробуйте следующее: #define while if void main() { while(1); }
@mihai: Здесь так сказано: § 17.6.1.3 ad 7: Identifiers that are keywords or operators in C++ shall not be defined as macros in C++ standard library headers., в сочетании с Одно правило определения. Таким образом, единственный выход - включить нет любые заголовки стандартной библиотеки и не использовать какой-либо код стандартной библиотеки.
Вы можете вернуть ссылку на переменную как часть функции. У него есть несколько применений, в основном для создания ужасного кода:
int s ;
vector <int> a ;
vector <int> b ;
int &G(int h)
{
if ( h < a.size() ) return a[h] ;
if ( h - a.size() < b.size() ) return b[ h - a.size() ] ;
return s ;
}
int main()
{
a = vector <int> (100) ;
b = vector <int> (100) ;
G( 20) = 40 ; //a[20] becomes 40
G(120) = 40 ; //b[20] becomes 40
G(424) = 40 ; //s becomes 40
}
Я знаю, что не должен спрашивать об этом, но почему голос против?
Я бы не назвал это скрытым - распространенными примерами являются операторы потока, operator* для итераторов, префикс ++, std::vector<T>::front(), составные присваивания, ...
Классные местные классы:
struct MyAwesomeAbstractClass
{ ... };
template <typename T>
MyAwesomeAbstractClass*
create_awesome(T param)
{
struct ans : MyAwesomeAbstractClass
{
// Make the implementation depend on T
};
return new ans(...);
}
довольно аккуратно, поскольку он не загрязняет пространство имен бесполезными определениями классов ...
Одна скрытая функция, даже скрытая для Разработчики GCC, - это инициализация элемента массива с помощью строкового литерала. Предположим, у вас есть структура, которая должна работать с массивом C, и вы хотите инициализировать член массива содержимым по умолчанию.
struct Person {
char name[255];
Person():name("???") { }
};
Это работает и работает только с массивами символов и инициализаторами строковых литералов. strcpy не требуется!
Может быть, это только я, но это кажется очевидным, учитывая, что void foo(){char p[255] = "daddy";} также является законным способом инициализации массива.
@PDaddy, это не слишком очевидно, потому что, хотя вещь = "foo" часто выполняется в коде, она почти никогда не выполняется для инициализаторов членов. Я никогда не видел этого раньше, пока не прочитал Стандарт и не обнаружил, что это разрешено и работает с другими компиляторами.
Не пытаясь быть аргументированным, но как новичок, это было следующее, что пришло в голову после попытки чего-то вроде class C{char p[255] = "daddy";}; и получения «только статические константные интегральные элементы данных могут быть инициализированы классом». Но в то время я только недавно узнал о списках инициализации, так что, возможно, это облегчило задачу. В любом случае, это, безусловно, наглядный пример того, чем списки инициализации отличаются от присваивания в теле конструктора.
Еще одна скрытая особенность заключается в том, что вы можете вызывать объекты класса, которые можно преобразовать в указатели на функции или ссылки. Разрешение перегрузки выполняется по их результатам, и аргументы передаются безупречно.
template<typename Func1, typename Func2>
class callable {
Func1 *m_f1;
Func2 *m_f2;
public:
callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
operator Func1*() { return m_f1; }
operator Func2*() { return m_f2; }
};
void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }
int main() {
callable<void(int), void(long)> c(foo, bar);
c(42); // calls foo
c(42L); // calls bar
}
Они называются «суррогатными функциями вызова».
Когда вы говорите, что по их результату выполняется разрешение перегрузки, вы имеете в виду, что он фактически преобразует его в оба функтора, а затем выполняет разрешение перегрузки? Я пробовал напечатать что-то в операторе Func1 * () и в операторе Func2 * (), но, похоже, он выбрал правильный, когда выяснил, какой оператор преобразования вызвать.
@navigator, да, он концептуально преобразуется в оба, а затем выбирает лучшее. На самом деле вызывать их не нужно, потому что он знает по типу результата, что они уже дадут. Фактический вызов совершается, когда выясняется, что было окончательно выбрано.
Еще одна скрытая функция, которая не работает в C, - это функциональность унарного оператора +. Вы можете использовать его для продвижения и разложения всяких вещей
+AnEnumeratorValue
И значение вашего перечислителя, которое раньше имело тип перечисления, теперь имеет идеальный целочисленный тип, который может соответствовать его значению. Вручную вы вряд ли узнаете этот тип! Это необходимо, например, когда вы хотите реализовать перегруженный оператор для перечисления.
Вы должны использовать класс, который использует статический инициализатор внутри класса без определения вне класса, но иногда он не может связать? Оператор может помочь создать временный объект, не делая предположений или зависимостей от его типа.
struct Foo {
static int const value = 42;
};
// This does something interesting...
template<typename T>
void f(T const&);
int main() {
// fails to link - tries to get the address of "Foo::value"!
f(Foo::value);
// works - pass a temporary value
f(+Foo::value);
}
Вы хотите передать функции два указателя, но это просто не сработает? Оператор может помочь
// This does something interesting...
template<typename T>
void f(T const& a, T const& b);
int main() {
int a[2];
int b[3];
f(a, b); // won't work! different values for "T"!
f(+a, +b); // works! T is "int*" both time
}
Правило доминирования полезно, но малоизвестно. В нем говорится, что даже если в неуникальном пути через решетку базового класса, поиск по имени частично скрытого члена уникален, если член принадлежит виртуальному базовому классу:
struct A { void f() { } };
struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };
// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };
Я использовал это для реализовать выравнивание-поддержку, который автоматически определяет строжайшее выравнивание с помощью правила доминирования.
Это относится не только к виртуальным функциям, но и к именам typedef, статическим / невиртуальным членам и всему остальному. Я видел, как он использовался для реализации перезаписываемых свойств в метапрограммах.
Аккуратный. Какая-то конкретная причина, по которой вы включили struct C в свой пример ...? Ваше здоровье.
@Devtron - Я видел несколько замечательных ошибок (например, неожиданное поведение), продаваемых как функции. Фактически, игровая индустрия на самом деле пытается сделать это в наши дни и называет это «эмерджентным геймплеем» (также проверьте «TK Surfing» от Psi-Ops, это была чистая ошибка, затем они оставили все как есть, и это было одним из лучшие особенности игры ИМХО)