Скрытые возможности C ++? [закрыто]


114

Нет любви C ++, когда речь идет о «скрытых особенностях» ряда вопросов? Подумал, что выкину его там. Какие скрытые возможности C ++?


@Devtron - Я видел несколько замечательных ошибок (например, неожиданное поведение), продаваемых как функции. На самом деле, игровая индустрия пытается сделать это в наши дни и называет это «эмерджентным геймплеем» (также проверьте «TK Surfing» из Psi-Ops, это была чистая ошибка, потом они оставили все как есть, и это один из лучшие особенности игры ИМХО)
Грант Питерс

5
@Laith J: Не очень многие люди прочитали 786-страничный стандарт ISO C ++ от корки до корки, но я полагаю, что вы читали и сохранили все это, верно?
j_random_hacker

2
@Laith, @j_random: См. Мой вопрос «Что такое шутка программиста, как я могу ее распознать и каков подходящий ответ» на stackoverflow.com/questions/1/you-have-been-link-rolled .

См. Meta.stackexchange.com/questions/56669/… , meta.stackexchange.com/questions/57226/… и соответствующие сообщения Meta.

Ответы:


308

Большинство программистов на C ++ знакомы с тернарным оператором:

x = (y < 0) ? 10 : 20;

Однако они не понимают, что его можно использовать как lvalue:

(a == 0 ? a : b) = 1;

что является сокращением для

if (a == 0)
    a = 1;
else
    b = 1;

Используйте с осторожностью :-)


11
Очень интересно. Я вижу, что это создает нечитаемый код.
Джейсон Бейкер

112
Хлоп. (а == 0? а: б) = (у <0? 10: 20);
Джаспер Беккерс

52
(b? trueCount: falseCount) ++
Павел Радзивиловский

12
Незнайка , если это GCC конкретные, но я был удивлен найти это тоже сработало: (value ? function1 : function2)().
Крис Берт-Браун,

3
@Chris Burt-Brown: Нет, это должно работать везде, если они имеют один и тот же тип (т.е. нет аргументов по умолчанию) function1и function2неявно преобразуются в указатели функций, а результат неявно преобразуется обратно.
MSalters

238

Вы можете без ошибок помещать URI в исходный код C ++. Например:

void foo() {
    http://stackoverflow.com/
    int bar = 4;

    ...
}

41
Но я подозреваю, что только по одному на каждую функцию? :)
Константин

51
@jpoh: http, за которым следует двоеточие, становится «меткой», которую вы позже используете в инструкции goto. вы получаете это предупреждение от своего компилятора, потому что оно не используется ни в одном операторе goto в приведенном выше примере.
utku_karatas

9
Вы можете добавить более одного, если у них разные протоколы! ftp.microsoft.com gopher: //aerv.nl/1 и так далее ...
Дэниел Эрвикер,

4
@Pavel: идентификатор, за которым следует двоеточие, - это метка (для использования goto, которая есть в C ++). Все, что следует за двумя косыми чертами, является комментарием. Следовательно, с http://stackoverflow.com, http- это метка (теоретически вы могли бы написать goto http;) и //stackoverflow.comпросто комментарий в конце строки. Оба они допустимы для C ++, поэтому конструкция компилируется. Конечно, он не делает ничего неопределенно полезного.
Дэвид Торнли

8
К сожалению goto http;, на самом деле не следует по URL. :(
Яков Галка

140

Указатель арифметики.

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

Но самый крутой C ++, который я когда-либо видел? Аналоговые литералы.


11
Избегаем указателей из-за ошибок? Указатели - это практически все, что связано с динамическим кодированием C ++!
Ник Бедфорд

1
Аналоговые литералы отлично подходят для обфусцированных записей конкурса C ++, особенно типа ASCII-art.
Synetech 05

119

Я согласен с большинством постов там: 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 в первом пункте в конце ответа вы упомянули члены интерфейса класса. вы имеете в виду «.. это как его члены-функции и друг функции , не являющиеся членами»?
Вильгельмелл 03


то, о чем вы говорите с 1, должно быть поиском koenig, верно?
Озгюр

1
@wilhelmtell: Нет-нет-нет ... :-p ... Я ДЕЙСТВИТЕЛЬНО имею в виду "его функции-члены и НЕ-ДРУГОВЫЕ функции, не являющиеся членами" .... Поиск Кенига позаботится о том, чтобы эти функции были рассмотрены раньше, чем другие " вне "функции в поиске символов
paercebal

7
Отличный пост, и +1 особенно к последней части, о которой мало кто осознает. Я бы, наверное, добавил библиотеку Boost как «скрытую функцию». Я в значительной степени считаю ее стандартной библиотекой, которую должен был иметь C ++. ;)
jalf

118

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

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );

1
Думаю, это полезно, если вы не хотите использовать using.
Siqi Lin

4
Это также полезно как способ переключения между реализациями, будь то выбор, скажем, потокобезопасный или небезопасный, или версия 1 против 2.
Тони Делрой

3
Это особенно полезно, если вы работаете над очень большим проектом с большими иерархиями пространств имен и не хотите, чтобы ваши заголовки вызывали загрязнение пространства имен (и вы хотите, чтобы объявления ваших переменных были удобочитаемыми).
Brandon Bohrer

102

В инициализации forцикла можно объявлять не только переменные , но также классы и функции.

for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
    ...
}

Это позволяет использовать несколько переменных разных типов.


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

2
Собственно, в этом контексте будет работать пара: for (std :: pair <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
Valentin Heinitz

2
@Valentin, тогда я рекомендую вам попытаться сделать отчет об ошибке VS2008 вместо того, чтобы голосовать против скрытой функции. Очевидно, это вина вашего компилятора.
Йоханнес Шауб - лит

2
Хм, в msvc10 тоже не работает. Как грустно :(
avakar

2
@avakar на самом деле, gcc представил ошибку, из-за которой он тоже отклоняется, в версии 4.6 :) см. gcc.gnu.org/bugzilla/show_bug.cgi?id=46791
Йоханнес Шауб - litb

77

Оператор массива ассоциативен.

A [8] является синонимом * (A + 8). Так как сложение ассоциативно, его можно переписать как * (8 + A), что является синонимом ..... 8 [A]

Вы не сказали, что полезно ... :-)


15
На самом деле, используя этот трюк, вы действительно должны обращать внимание на то, какой тип вы используете. A [8] на самом деле является 8-м A, а 8 [A] - целым числом Ath, начинающимся с адреса 8. Если A является байтом, у вас есть ошибка.
Винсент Роберт

38
Вы имеете в виду "коммутативный", когда говорите "ассоциативный"?
DarenW

28
Винсент, ты ошибаешься. Тип Aне имеет значения. Например, если бы Aбыли a char*, код все равно был бы действителен.
Конрад Рудольф

11
Помните, что A должен быть указателем, а не оператором перегрузки класса [].
Дэвид Родригес - dribeas 03

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

73

Мало что известно о том, что союзы тоже могут быть шаблонами:

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

j_random_hacker ах, да это чушь. хороший улов. Я написал это как структуру. подожди, я исправлю это
Йоханнес Шауб - litb

Разве это не вызывает неопределенное поведение?
Грег Бэкон

7
@gbacon, да, он вызывает неопределенное поведение, если Fromи Toустановлены и используются соответствующим образом. Однако такое объединение можно использовать с определенным поведением (с Toмассивом беззнаковых символов или структурой, совместно использующей исходную последовательность From). Даже если вы используете его неопределенным образом, он все равно может быть полезен для низкоуровневой работы. В любом случае, это всего лишь один пример шаблона объединения - могут быть другие варианты использования шаблонного объединения.
Йоханнес Шауб - лит

3
Осторожнее с конструктором. Обратите внимание, что вам нужно создать только первый элемент, и это разрешено только в C ++ 0x. Согласно текущему стандарту вы должны придерживаться тривиально конструируемых типов. И никаких деструкторов.
Potatoswatter

72

C ++ - это стандарт, никаких скрытых функций быть не должно ...

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


65

Еще одна скрытая функция, которая не работает в 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
}

61

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

const MyClass& x = MyClass(); // temporary exists as long as x is in scope

3
Вы можете уточнить? Как это вы просто дразните;)
Джозеф Гарвин

8
ScopeGuard ( ddj.com/cpp/184403758 ) - отличный пример, который использует эту функцию.
MSN

2
Я с Джозефом Гарвином. Пожалуйста, просветите нас.
Питер Мортенсен

Я только что сделал в комментариях. Кроме того, это естественное следствие использования ссылочного параметра const.
MSN


52

Приятной особенностью, которая используется нечасто, является блок try-catch для всей функции:

int Function()
try
{
   // do something here
   return 42;
}
catch(...)
{
   return -1;
}

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


Я не думаю, что вы можете returnпоймать блок Function Try, только перебросить.
Константин

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

7
Возврат запрещен только для строителей. Блок try функции конструктора будет обнаруживать ошибки при инициализации базы и членов (единственный случай, когда блок try функции делает что-то иное, чем просто попытка внутри функции); отказ от повторного броска приведет к получению неполного объекта.
puetzk

Да. Это очень полезно. Я написал макросы BEGIN_COM_METHOD и END_COM_METHOD для перехвата исключений и возврата HRESULTS, чтобы исключения не просачивались из класса, реализующего COM-интерфейс. Это сработало.
Скотт Лэнгхэм

3
Как указывает @puetzk, это единственный способ обрабатывать исключения, вызываемые чем-либо в списке инициализаторов конструктора , например конструкторами базовых классов или конструкторами членов данных.
anton.burger

44

Многие знают о метафункции 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;
bames53 09

42

Довольно скрытая особенность заключается в том, что вы можете определять переменные в условии 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

puetzk, нет, нет. но рад, что вам понравилось :)
Йоханнес Шауб - litb

4
@Frerich, это вообще невозможно в коде C. Я думаю, вы думаете об этом if((a = f()) == b) ..., но этот ответ фактически объявляет переменную в условии.
Йоханнес Шауб - лит

1
@Angry это совсем другое, потому что объявление переменной сразу проверяется на его логическое значение. Также есть сопоставление с циклами for, которое выглядит так, как будто for(...; int i = foo(); ) ...;This будет проходить через тело, пока iистинно, инициализируя его каждый раз снова. Цикл, который вы показываете, просто демонстрирует объявление переменной, но не объявление переменной, которое одновременно действует как условие :)
Йоханнес Шауб - litb

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

29

Предотвращение вызова оператора запятой перегрузок оператора

Иногда вы правильно используете оператор запятой, но вы хотите, чтобы ни один пользовательский оператор запятой не мешал, потому что, например, вы полагаетесь на точки последовательности между левой и правой стороной или хотите убедиться, что ничто не мешает желаемому действие. Вот где void()вступает в игру:

for(T i, j; can_continue(i, j); ++i, void(), ++j)
  do_code(i, j);

Игнорируйте заполнители, которые я поставил для условия и кода. Важно то void(), что заставляет компилятор использовать встроенный оператор запятой. Иногда это может быть полезно при реализации классов свойств.


Я просто использовал это, чтобы покончить со своим игнорирующим выражением лица . :)
GManNickG

28

Инициализация массива в конструкторе. Например, в классе, если у нас есть массив intas:

class clName
{
  clName();
  int a[10];
};

Мы можем инициализировать все элементы в массиве по умолчанию (здесь все элементы массива равны нулю) в конструкторе как:

clName::clName() : a()
{
}

6
Вы можете сделать это с любым массивом где угодно.
Potatoswatter

@Potatoswatter: сложнее, чем кажется, из-за самого неприятного синтаксического анализа. Я не могу придумать, где еще это можно было бы сделать, кроме, может быть, возвращаемого значения
Mooing Duck

Если тип массива является типом класса, тогда это не нужно, верно?
Томас Эдинг

27

Ооо, вместо этого я могу составить список ненависти к животным:

  • Деструкторы должны быть виртуальными, если вы собираетесь использовать полиморфно.
  • Иногда члены инициализируются по умолчанию, иногда нет.
  • Локальные классы нельзя использовать в качестве параметров шаблона (что делает их менее полезными)
  • спецификаторы исключения: выглядят полезными, но не
  • перегрузки функций скрывают функции базового класса с разными сигнатурами.
  • нет полезной стандартизации для интернационализации (переносимая стандартная широкая кодировка, кто-нибудь? Нам придется подождать до C ++ 0x)

С положительной стороны

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

16
домашняя ненависть: нет определенного ABI для приложений C ++, в отличие от C-приложений, которые все используют, потому что каждый язык может гарантировать вызов функции C, никто не может сделать то же самое для C ++.
gbjbaanb

8
Деструкторы должны быть виртуальными только в том случае, если вы намереваетесь уничтожать полиморфно, что немного отличается от первого пункта.
Дэвид Родригес - dribeas, 03

2
В C ++ 0x локальные типы могут использоваться в качестве параметров шаблона.
tstenner

1
В C ++ 0x деструкторы будут виртуальными, если у объекта есть какие-либо виртуальные функции (например, vtable).
Macke

не забывайте NRVO, и, конечно, разрешена любая оптимизация, если она не меняет вывод программы
jk.

26

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

Обычно 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);
}


26

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

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
}

Они называются «суррогатными функциями вызова».


1
Когда вы говорите, что по их результатам выполняется разрешение перегрузки, вы имеете в виду, что оно фактически преобразует его в оба функтора, а затем выполняет разрешение перегрузки? Я пробовал напечатать что-то в операторе Func1 * () и в операторе Func2 * (), но, похоже, он выбрал правильный, когда выяснил, какой оператор преобразования вызвать.
навигатор

3
@navigator, да, он концептуально преобразуется в оба, а затем выбирает лучшее. На самом деле вызывать их не нужно, потому что он знает из типа результата, что они уже дадут. Фактический вызов совершается, когда выясняется, что было окончательно выбрано.
Йоханнес Шауб - лит

26

Скрытые возможности:

  1. Чистые виртуальные функции могут иметь реализацию. Типичный пример, чистый виртуальный деструктор.
  2. Если функция генерирует исключение, не указанное в ее спецификациях исключения, но функция имеет std::bad_exceptionв своей спецификации исключения, исключение преобразуется std::bad_exceptionи генерируется автоматически. Таким образом вы хотя бы узнаете, что bad_exceptionбыл брошен. Подробнее читайте здесь .

  3. функциональные блоки try

  4. Ключевое слово template для устранения неоднозначности typedef в шаблоне класса. Если имя шаблон члена специализации появляется после ., ->или ::оператора, и это имя имеет явно квалифицированные параметры шаблона, префикс имя шаблона члена с ключевыми словами шаблона. Подробнее читайте здесь .

  5. Значения по умолчанию для параметров функции могут быть изменены во время выполнения. Подробнее читайте здесь .

  6. A[i] работает так же хорошо, как i[A]

  7. Временные экземпляры класса можно изменять! Неконстантная функция-член может быть вызвана для временного объекта. Например:

    struct Bar {
      void modify() {}
    }
    int main (void) {
      Bar().modify();   /* non-const function invoked on a temporary. */
    }

    Подробнее читайте здесь .

  8. Если два разных типа присутствуют до и после выражения оператора :ternary ( ?:), то результирующий тип выражения является наиболее общим из двух. Например:

    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]
abelenky

Я получаю коммутацию, просто это означает, что [] не имеет собственного семантического значения и просто эквивалентен замене в макро-стиле, где «x [y]» заменяется на «(* ((x) + (y )))». Совсем не то, что я ожидал. Интересно, почему это так определяется.
P Daddy

Обратная совместимость с C
jmucchiello

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

24

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 ++ этого не знают.


11
И на противоположном конце вы не можете использовать operator [] на карте const
Дэвид Родригес - dribeas

2
+1 за Ника, люди могут сойти с ума, если не знают об этом .find().
LiraNuna

или « const map::operator[]генерирует сообщения об ошибках»
просто кто-нибудь

2
Это не особенность языка, это особенность стандартной библиотеки шаблонов. Это также довольно очевидно, поскольку operator [] возвращает действительную ссылку.
Рамон Заразуа Б.

2
Мне пришлось какое-то время использовать карты на C #, где карты не ведут себя таким образом, чтобы понять, что это особенность. Я думал, что меня это раздражает больше, чем я им пользовался, но, похоже, я ошибался. Мне его не хватает на С #.
sbi

20

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


«обесценивает» - сильный термин…
Potatoswatter

@Potato: Старый комментарий, я знаю, но в стандарте говорится, что использование static в области пространства имен не рекомендуется, с предпочтением для безымянных пространств имен.
GManNickG

@GMan: нет проблем, я не думаю, что ТАК страницы действительно "умирают". По обе стороны истории, staticв глобальном масштабе это никоим образом не является устаревшим. (Для справки: C ++ 03 §D.2)
Potatoswatter

Ах, при более внимательном чтении: «Имя, объявленное в глобальном пространстве имен, имеет глобальную область действия (также называемую глобальной областью)». Это действительно так?
Potatoswatter

@ Картофель: Ага. :) staticиспользование должно использоваться только внутри класса или функции.
GManNickG

19

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

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 ++ Templates: The Complete Guide / Section 8.4


18

функции void могут возвращать значения void

Малоизвестно, но следующий код подходит

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; !
};

17

Считываем файл в вектор строк:

 vector<string> V;
 copy(istream_iterator<string>(cin), istream_iterator<string>(),
     back_inserter(V));

istream_iterator


8
Или: vector <string> V ((istream_iterator <string> (cin)), istream_iterator <string>);
UncleBens,

5
Вы имеете в виду vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());- отсутствующие скобки после второго параметра
knittl

1
На самом деле это не скрытая функция C ++. Больше возможностей STL. STL! = Язык
Ник Бедфорд

14

Вы можете использовать шаблоны битовых полей.

template <size_t X, size_t Y>
struct bitfield
{
    char left  : X;
    char right : Y;
};

Я еще не придумал для этого какой-либо цели, но это, черт возьми, меня удивило.


1
См. Здесь, где я недавно предложил это для n-битной арифметики: stackoverflow.com/questions/8309538/…
см.

14

Одна из самых интересных грамматик любых языков программирования.

Три из этих вещей принадлежат друг другу, а две совершенно разные ...

SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));

Все, кроме третьего и пятого, определяют SomeTypeобъект в стеке и инициализируют его (с помощью uв первых двух случаях и конструктором по умолчанию в четвертом. Третий объявляет функцию, которая не принимает параметров и возвращает a SomeType. Пятый аналогично объявляет функция, которая принимает один параметр по значению SomeTypeуказанного типа u.


есть ли разница между 1-м и 2-м? хотя я знаю, что это обе инициализации.
Özgür

Comptrol: Я так не думаю. Оба в конечном итоге вызовут конструктор копирования, хотя первый ВЫГЛЯДИТ как оператор присваивания, на самом деле это конструктор копирования.
abelenky 07

1
Если u является типом, отличным от SomeType, то первый вызовет сначала конструктор преобразования, а затем конструктор копирования, тогда как второй вызовет только конструктор преобразования.
Eclipse,

3
1-й - неявный вызов конструктора, 2-й - явный вызов. Посмотрите на следующий код, чтобы увидеть разницу: #include <iostream> class sss {public: explicit sss (int) {std :: cout << "int" << std :: endl; }; sss (double) {std :: cout << "double" << std :: endl; }; }; int main () {sss ddd (7); // вызывает конструктор int sss xxx = 7; // вызывает двойной конструктор return 0; }
Кирилл В. Лядвинский

Верно - первая строка не будет работать, если конструктор объявлен явно.
Eclipse,

12

Избавляемся от форвардных объявлений:

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;

2
FStruct s = {};еще короче.
Константин

В последнем примере было бы проще: a (); б (); float c = 1.0f;
Zifre

2
Этот синтаксис "float c = (a (), b (), 1.0f);" полезен для акцентирования внимания на операции присвоения (присвоение "c"). Операции присваивания важны в программировании, потому что они с меньшей вероятностью станут устаревшими IMO. Не знаю почему, это может быть связано с функциональным программированием, когда состояние программы переназначается каждый кадр. PS. И нет, «int d = (11,22,1.0f)» будет равно «1». Проверено минуту назад с VS2008.
AareP

2
+1 Разве ты не должен звонить main ? Я бы предложил global().main();и просто забыл о синглтоне ( вы можете просто работать с временным, что продлевает его срок службы )
см.

1
Я сомневаюсь, что присвоение ссылок переносимо. Тем не менее, мне нравится, что структура отклоняет предварительные объявления.
Thomas Eding

12

Тернарный условный оператор ?:требует, чтобы его второй и третий операнды имели "приемлемые" типы (говоря неформально). Но это требование имеет одно исключение (каламбур): второй или третий операнд может быть выражением throw (имеющим типvoid ), независимо от типа другого операнда.

Другими словами, можно написать следующие действительно корректные выражения C ++, используя ?:оператор

i = a > b ? a : throw something();

Кстати, тот факт, что выражение throw на самом деле является выражением (типа void), а не оператором, является еще одной малоизвестной особенностью языка C ++. Это, помимо прочего, означает, что следующий код абсолютно верен

void foo()
{
  return throw something();
}

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


Как бы то ни было, у Нила есть вопрос по этому поводу: stackoverflow.com/questions/1212978/… , просто для дополнительной информации.
GManNickG

12

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

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, статическим / не виртуальным членам и всему остальному. Я видел, как это использовалось для реализации перезаписываемых свойств в метапрограммах.


1
Ухоженная. Какую-то конкретную причину вы включили struct Cв свой пример ...? Приветствия.
Тони Делрой
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.