Скрытые особенности C ++?

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

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

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

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

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

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

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

Используйте с осторожностью 🙂

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

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

Арифметика указателей.

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

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

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

Большинство из этих объектов не являются встроенными функциями языка, а являются библиотечными.

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

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

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

Другие используют использование множественной парадигмы для создания «способов программирования» вне предка C ++, то есть C.

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

Используя шаблоны , вы можете создавать код, который будет работать на большинстве типов, в том числе не тот, который вы считали вначале. Вы также можете повысить безопасность типов (например, автоматическое создание malloc / realloc / free). Объекты C ++ действительно мощные (и, следовательно, опасные, если они используются небрежно), но даже динамический polymorphism имеет свою статическую версию в C ++: CRTP .

Я обнаружил, что большинство книг « Эффективный C ++ » из книг Скотта Мейерса или « Исключительных C ++ » из Herb Sutter, которые легко читаются, и довольно сокровищницы информации об известных и менее известных функциях C ++.

Среди моих предпочтений – тот, который должен заставить волосы любого программиста Java подняться от ужаса: на C ++ наиболее объектно-ориентированный способ добавления функции к объекту – это функция не члена, отличная от друга, вместо того, function (т.е. метод classа), потому что:

  • В C ++ интерфейс classа – это как его функции-члены, так и функции, не входящие в одно и то же пространство имен

  • не связанные друг с другом функции не имеют привилегированного доступа к внутреннему classу. Таким образом, использование функции-члена над не-членом, не являющимся другом, ослабит инкапсуляцию classа.

Это никогда не удивляет даже опытных разработчиков.

(Источник: среди прочих, онлайн-гуру Херба Саттера на неделе № 84: http://www.gotw.ca/gotw/084.htm )

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

 namespace fs = boost::filesystem; fs::path myPath( strPath, fs::native ); 

Не только переменные могут быть объявлены в части init цикла for , но также и в classах и функциях.

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

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

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

A [8] является синонимом * (A + 8). Поскольку добавление ассоциативно, это можно переписать как * (8 + A), что является синонимом для ….. 8 [A]

Вы не сказали полезного … 🙂

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

 template union union_cast { From from; To to; union_cast(From from) :from(from) { } To getTo() const { return to; } }; 

И они могут иметь конструкторы и функции-члены. Просто ничего, что связано с наследованием (включая виртуальные функции).

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

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

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

Преобразование enums в целое число

 +AnEnumeratorValue 

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

Получить значение из переменной

Вы должны использовать class, который использует статический инициализатор в classе без определения classа, но иногда он не может ссылаться? Оператор может помочь создать временное, не создавая предположений или зависимостей от его типа

 struct Foo { static int const value = 42; }; // This does something interesting... template 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 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 } 

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

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

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

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

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

Многие знают о метафайле identity / id , но для него не подходит шаблоны: простота написания объявлений:

 // void (*f)(); // same id::type *f; // void (*f(void(*p)()))(int); // same id::type *f(id::type *p); // int (*p)[2] = new int[10][2]; // same id::type *p = new int[10][2]; // void (C::*p)(int) = 0; // same id::type C::*p = 0; 

Это помогает значительно расшифровать объявления C ++!

 // boost::identity is pretty much the same template struct id { typedef T type; }; 

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

 if(int * p = getPointer()) { // do something } 

Некоторые macros используют это, например, чтобы обеспечить некоторую «заблокированную» область действия следующим образом:

 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()) { // ... } 

(а также в условии a). Но я не слишком уверен, что все это полезно 🙂

Предотrotation переадресации оператора запятой

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

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

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

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

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

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

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

Ооо, я могу придумать список домашних ненависти вместо:

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

На стороне плюса

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

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

Обычно C ++ запрещает вам доступ к нестатическим защищенным членам объекта classа, даже если этот class является вашим базовым classом

 struct A { protected: int a; }; struct B : A { // error: can't access protected member static int get(A &x) { return xa; } }; struct C : A { }; 

Это запрещено: вы и компилятор не знаете, на что ссылается ссылка. Это может быть объект C , и в этом случае class B не имеет бизнеса и не знает о его данных. Такой доступ предоставляется только в том случае, если x является ссылкой на производный class или один полученный из него. И это может позволить произвольному fragmentу кода читать любой защищенный член, просто составляя class «выброса», который считывает элементы, например std::stack :

 void f(std::stack &s) { // now, let's decide to mess with that stack! struct pillager : std::stack { static std::deque &get(std::stack &s) { // error: stack::c is protected return sc; } }; // haha, now let's inspect the stack's middle elements! std::deque &d = pillager::get(s); } 

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

 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 &s) { // now, let's decide to mess with that stack! struct pillager : std::stack { static std::deque &get(std::stack &s) { return s.*(pillager::c); } }; // haha, now let's inspect the stack's middle elements! std::deque &d = pillager::get(s); } 

Это будет еще проще с использованием объявления в производном classе, что делает имя участника общедоступным и относится к члену базового classа.

 void f(std::stack &s) { // now, let's decide to mess with that stack! struct pillager : std::stack { using std::stack::c; }; // haha, now let's inspect the stack's middle elements! std::deque &d = s.*(&pillager::c); } 

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

 template 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 c(foo, bar); c(42); // calls foo c(42L); // calls bar } 

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

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

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

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

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

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

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

  7. Временные экземпляры classа могут быть изменены! Функция non-const member может быть вызвана на временном объекте. Например:

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

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

  8. Если присутствуют два разных типа до и после : в тернарном ( ?: 🙂 Выражении оператора, получившийся тип выражения является тем, который является самым общим из двух. Например:

     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) } 

map::operator[] создает запись, если отсутствует ключ, и возвращает ссылку на значение, созданное по умолчанию. Поэтому вы можете написать:

 map 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; 

Я поражен тем, как многие программисты на С ++ этого не знают.

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

Defining ordinary friend functions in class templates needs special attention:

 template  class Creator { friend void appear() { // a new function ::appear(), but it doesn't … // exist until Creator is instantiated } }; Creator miracle; // ::appear() is created at this point Creator oops; // ERROR: ::appear() is created a second time! 

In this example, two different instantiations create two identical definitions—a direct violation of the ODR

We must therefore make sure the template parameters of the class template appear in the type of any friend function defined in that template (unless we want to prevent more than one instantiation of a class template in a particular file, but this is rather unlikely). Let’s apply this to a variation of our previous example:

 template  class Creator { friend void feed(Creator*){ // every T generates a different … // function ::feed() } }; Creator one; // generates ::feed(Creator*) Creator two; // generates ::feed(Creator*) 

Disclaimer: I have pasted this section from C++ Templates: The Complete Guide / Section 8.4

void functions can return void values

Little known, but the following code is fine

 void f() { } void g() { return f(); } 

Aswell as the following weird looking one

 void f() { return (void)"i'm discarded"; } 

Knowing about this, you can take advantage in some areas. One example: void functions can’t return a value but you can also not just return nothing, because they may be instantiated with non-void. Instead of storing the value into a local variable, which will cause an error for void , just return a value directly

 template struct sample { // assume f may return void T dosomething() { return f(); } // better than T t = f(); /* ... */ return t; ! }; 

Read a file into a vector of strings:

  vector V; copy(istream_iterator(cin), istream_iterator(), back_inserter(V)); 

istream_iterator

You can template bitfields.

 template  struct bitfield { char left : X; char right : Y; }; 

I have yet to come up with any purpose for this, but it sure as heck surprised me.

One of the most interesting grammars of any programming languages.

Three of these things belong together, and two are something altogether different…

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

All but the third and fifth define a SomeType object on the stack and initialize it (with u in the first two case, and the default constructor in the fourth. The third is declaring a function that takes no parameters and returns a SomeType . The fifth is similarly declaring a function that takes one parameter by value of type SomeType named u .

Getting rid of forward declarations:

 struct global { void main() { a = 1; b(); } int a; void b(){} } singleton; 

Writing switch-statements with ?: operators:

 string result = a==0 ? "zero" : a==1 ? "one" : a==2 ? "two" : 0; 

Doing everything on a single line:

 void a(); int b(); float c = (a(),b(),1.0f); 

Zeroing structs without memset:

 FStruct s = {0}; 

Normalizing/wrapping angle- and time-values:

 int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150 

Assigning references:

 struct ref { int& r; ref(int& r):r(r){} }; int b; ref a(b); int c; *(int**)&a = &c; 

The ternary conditional operator ?: requires its second and third operand to have “agreeable” types (speaking informally). But this requirement has one exception (pun intended): either the second or third operand can be a throw expression (which has type void ), regardless of the type of the other operand.

In other words, one can write the following pefrectly valid C++ expressions using the ?: operator

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

BTW, the fact that throw expression is actually an expression (of type void ) and not a statement is another little-known feature of C++ language. This means, among other things, that the following code is perfectly valid

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

although there’s not much point in doing it this way (maybe in some generic template code this might come handy).

The dominance rule is useful, but little known. It says that even if in a non-unique path through a base-class lattice, name-lookup for a partially hidden member is unique if the member belongs to a virtual base-class:

 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(); } }; 

I've used this to implement alignment-support that automatically figures out the strictest alignment by means of the dominance rule.

This does not only apply to virtual functions, but also to typedef names, static/non-virtual members and anything else. I've seen it used to implement overwritable traits in meta-programs.

Давайте будем гением компьютера.