Приховані особливості 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-roll .

Див. Meta.stackexchange.com/questions/56669/… , meta.stackexchange.com/questions/57226/… та пов'язані з ними публікації Meta.

Відповіді:


308

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

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

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

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

який є скороченим

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

Використовуйте обережно :-)


11
Дуже цікаво. Я бачу, що робити якийсь нечитабельний код, хоча.
Джейсон Бейкер

112
Yikes. (a == 0? a: b) = (y <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 і так далі ...
Daniel Earwicker

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

119

Я погоджуюсь з більшістю публікацій там: C ++ - це багатопарадигмальна мова, тому "приховані" функції, які ви знайдете (окрім "невизначених форм поведінки", яких вам слід уникати будь-якою ціною), - це вміле використання засобів.

Більшість із цих засобів - це не вбудовані функції мови, а бібліотечні.

Найважливішим є RAII , який часто ігнорують роками розробники C ++, що надходять зі світу C. Перевантаження оператора - це часто неправильно зрозуміла функція, яка включає як поведінку, подібну до масиву (оператор підписки), так і вказівні операції (смарт-покажчики) та вбудовані схожі операції (множення матриць.

Використання винятку часто є важким, але при певній роботі може створити дійсно надійний код через безпеку виключень специфікацій (включаючи код, який не вийде з ладу, або матиме функції, схожі на виконання зобов’язань, які будуть успішними або повертаються назад до її первісний стан).

Найвідомішою з "прихованих" особливостей C ++ є метапрограмування шаблонів , оскільки це дозволяє вам частково (або повністю) програму виконати під час компіляції замість виконання. Однак це важко, і ви повинні добре зрозуміти шаблони, перш ніж спробувати.

Інші використовують багаторазову парадигму для створення "способів програмування" поза родоначальником C ++, тобто С.

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

Використовуючи шаблони , ви можете створити код, який буде працювати на більшості типів, включаючи не той, про який ви думали спочатку. Ви також можете підвищити безпеку типу (наприклад, автоматизований typesafe malloc / realloc / free). Особливості об'єкта C ++ дійсно потужні (і, отже, небезпечні при необережному використанні), але навіть динамічний поліморфізм має свою статичну версію в C ++: CRTP .

Я виявив, що більшість книг типу " Ефективні C ++ " від Скотта Майєрса або " Винятковий С ++ " книг від Herb Sutter є просто читатими, а також досить скарбами інформації про відомі та менш відомі функції C ++.

Серед моїх переваг є той, який повинен змусити волосся будь-якого програміста Java піднятися від жаху: У C ++ найбільш об'єктно-орієнтований спосіб додати функцію до об’єкта - через функцію, що не є другою, а не член-член, функція (тобто метод класу), оскільки:

  • У C ++ інтерфейс класу - це як його членські функції, так і нечленні функції в одному просторі імен

  • Недружні функції, які не є членами, не мають привілейованого доступу до внутрішнього класу. Таким чином, використання функції члена над недругом, який не є членом, послабить інкапсуляцію класу.

Це ніколи не здивує навіть досвідчених розробників.

(Джерело: Серед інших, Інтернет-гуру Тижня № 84 Герба Саттера: http://www.gotw.ca/gotw/084.htm )


+1 дуже ретельна відповідь. це неповно з очевидних причин (інакше більше не буде "прихованих особливостей"!): p в першому пункті наприкінці відповіді ви згадали членів інтерфейсу класу. ти маєш на увазі ".. це і його функції-члени, і функції друга, які не є членами"?
wilhelmtell


те, про що ви згадуєте з 1, має бути пошук koenig, правда?
Özgür

1
@wilhelmtell: Ні, ні ні ... :-p ... Я НЕ маю на увазі "його функції-члени та НЕ-ДРУЗІ-не-члени". зовні "функціонує в пошуку символів
paercebal

7
Чудовий пост та +1 особливо для останньої частини, про яку далеко мало хто розуміє. Я б, мабуть, додав бібліотеку Boost як "приховану функцію". Я майже вважаю це стандартною бібліотекою, яку повинен був мати C ++. ;)
джельф

118

Однією з мовних особливостей, яку я вважаю дещо прихованою, оскільки я ніколи не чув про це протягом усього свого навчання в школі, - псевдонім простору імен. Це не було мені до відома, поки я не зіткнувся з прикладами цього в документації щодо підвищення. Звичайно, тепер, коли я знаю про нього, ви можете знайти його в будь-якій стандартній довідці C ++.

namespace fs = boost::filesystem;

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

1
Я думаю, це корисно, якщо ви не хочете використовувати using.
Сікі Лін

4
Це також корисно як спосіб перемикання між реалізаціями, незалежно від того, вибрати "безпечний потік" на "безпечний потік" або "версія 1" проти "2."
Тоні Делрой

3
Це особливо корисно, якщо ви працюєте над дуже великим проектом з великими ієрархіями простору імен і не хочете, щоб ваші заголовки викликали забруднення простору імен (і ви хочете, щоб ваші змінні декларації були зрозумілими для людини).
Брендон Борер

102

В ініціальній частині forциклу можуть бути оголошені не тільки змінні , але й класи та функції.

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

Це дозволяє мати декілька змінних різних типів.


31
Приємно знати, що ти можеш це зробити, але особисто я дійсно намагаюся уникати нічого подібного. Переважно тому, що важко читати.
Zoomulator

2
Власне, для цього в цьому контексті використовується пара: для (std :: пара <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
Валентин Хайніц

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

2
Гм, він також не працює в msvc10. Як сумно :(
avakar

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

77

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

A [8] є синонімом * (A + 8). Оскільки додавання є асоціативним, його можна переписати як * (8 + A), що є синонімом для ..... 8 [A]

Ти не сказав корисного ... :-)


15
Насправді, використовуючи цей трюк, ви дійсно повинні звертати увагу на те, який тип ви використовуєте. A [8] насправді є 8-м A, тоді як 8 [A] є цілим числом Ath, починаючи з адреси 8. Якщо A - байт, у вас є помилка.
Вінсент Роберт

38
ти маєш на увазі "комутативний", де ти кажеш "асоціативний"?
DarenW

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

11
Остерігайтеся, що A повинен бути вказівником, а не оператором перевантаження класу [].
Девід Родрігес - дрибес

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 о, правда, це нісенітниця. хороший улов. я написав це так, як це буде структура. зачекаю, що я це
виправлю

Чи це не викликає невизначеної поведінки?
Грег Бекон

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

Про те, що мало хто знає, тривалість життя тимчасових посилань, на які посилаються const-посилання. Або, принаймні, це моя улюблена частина знання 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

Приємною особливістю, яка часто не використовується, є блок-пробний блок на всій функції:

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

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


Я не думаю, що ти можеш returnзловити блок функцій Спробуйте, лише перезавантажте.
Константин

Я просто спробував скласти вищезгадане, і це не дало попередження. Я думаю, що приклад, наведений вище, працює.
vividos

7
повернення заборонено лише для конструкторів. Блок спробу функціонування конструктора буде виявляти помилки, ініціалізуючи базу та члени (єдиний випадок, коли блок спробу функцій робить щось інше, ніж просто спробувати всередині функції); не повторне кидання призведе до неповного об'єкта.
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; };

Цікаво, але спочатку у мене було більше проблем з читанням деяких цих визначень. Ще один спосіб виправити проблему з внутрішньою 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;
стороною

42

Досить прихованою особливістю є те, що ви можете визначити змінні в умові if, і його область буде охоплюватись лише над if та іншими блоками:

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(SomeThing t = getSomeThing()) {
    // ...
}

(а також в умові). Але я не надто впевнений, чи всі вони такі корисні :)


Акуратно! Я ніколи не знав, що ти можеш це зробити ... це дозволило б (і дозволить) зберегти клопоти під час написання коду зі значеннями повернення помилок. Чи є ще спосіб мати умовний, а не просто! = 0 у такому вигляді? якщо ((int r = func ()) <0), здається, не працює ...
puetzk

puetzk, ні там немає. але радий, що вам це подобається :)
Йоханнес Шауб - ліб

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

1
@Angry - це зовсім інше, тому що декларація змінної тестується на її бульне значення відразу. Існує також відображення для циклів for-петлі, схоже, що for(...; int i = foo(); ) ...;це буде проходити через тіло, доки iце правда, ініціалізуючи його кожного разу знову. Цикл, який ви показуєте, просто демонструє змінну декларацію, але не змінну декларацію, яка одночасно виступає умовою :)
Йоханнес Шауб - litb

5
Дуже добре, за винятком того, що ви не згадали про те, що призначення для цієї функції було призначено для динамічних кидок покажчиків, я вважаю.
mmocny

29

Заборона оператору комами викликати перевантаження оператора

Іноді ви дійсно використовуєте оператор комами, але хочете переконатись, що жоден визначений користувачем оператор кома не заважає, оскільки, наприклад, ви покладаєтесь на точки послідовності між лівою та правою стороною або хочете переконатися, що нічого не заважає бажаному дії. Ось тут void()вступає гра:

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

Ігноруйте посадочні місця, які я поставив, за умовою та кодом. Важливо те void(), що змушує компілятор використовувати вбудований оператор кома. Це може бути корисно при впровадженні класів рис, іноді теж.


Я просто використав це, щоб закінчити мій ігнор виразності надмірності . :)
GManNickG

28

Ініціалізація масиву в конструкторі. Наприклад, у класі, якщо у нас є масив intяк:

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

Ми можемо ініціалізувати всі елементи масиву до його за замовчуванням (тут усі елементи масиву до нуля) у конструкторі:

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

6
Це можна зробити з будь-якого масиву в будь-якому місці.
Potatoswatter

@Potatoswatter: важче, ніж це виглядає, через самий неприємний аналіз. Я не можу придумати ще ніде, що можна зробити, крім, можливо, зворотного значення
Mooing Duck

Якщо тип масиву - це тип класу, то це не потрібно?
Томас Едінг

27

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

  • Якщо ви плануєте використовувати поліморфно, деструктори повинні бути віртуальними
  • Іноді члени ініціалізуються за замовчуванням, іноді - ні
  • Місцеві фрази не можна використовувати як параметри шаблону (робить їх менш корисними)
  • специфікатори винятків: виглядають корисними, але ні
  • функція перевантаження приховує функції базового класу з різними підписами.
  • немає корисної стандартизації щодо інтернаціоналізації (кому-небудь портативний стандартний широкий діапазон? Доведеться почекати, поки C ++ 0x)

З плюсу

  • прихована функція: функціонування блоків спробу. На жаль, я не знайшов для цього використання. Так, я знаю, чому вони додали його, але вам доведеться заново вбудувати конструктор, що робить це безглуздим.
  • Варто уважно ознайомитись із гарантіями STL щодо дійсності ітератора після модифікації контейнера, що дозволяє вам зробити трохи приємніші петлі.
  • Підвищення - навряд чи секрет, але його варто використовувати.
  • Оптимізація зворотного значення (не очевидно, але це спеціально дозволено стандартом)
  • Функтори aka функціонують об'єкти aka operator (). Це широко використовується STL. насправді не секрет, але це чудовий побічний ефект перевантаження операторів та шаблонів.

16
ненависть до домашніх тварин: не визначено ABI для додатків C ++, на відміну від тих, що використовуються C, оскільки кожна мова може гарантувати виклик функції C, ніхто не може робити те ж саме для C ++.
gbjbaanb

8
Деструктори повинні бути віртуальними лише в тому випадку, якщо ви маєте намір знищити поліморфно, що трохи тонко відрізняється від першої точки.
Девід Родрігес - дрибес

2
За допомогою параметрів шаблону C ++ 0x можуть використовуватися локальні типи.
tstenner

1
З C ++ 0x деструктори будуть віртуальними, якщо об’єкт має будь-які віртуальні функції (тобто vtable).
Макке

не забувайте про 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);
}

Це стане ще простішим із застосуванням декларації у похідному класі, що робить ім’я учасника загальнодоступним та посилається на члена базового класу.

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
Коли ви говорите, що роздільна здатність перевантаження робиться за результатами їх, ви маєте на увазі, що вона фактично перетворює її на обидва Functors, а потім робить роздільну здатність перевантаження? Я спробував надрукувати щось в операторі Func1 * () та операторі Func2 * (), але, здається, вибрав правильний, коли з'ясує, до якого оператора перетворення потрібно викликати.
Штурман

3
@navigator, так це концептуально перетворюється на обидва, а потім вибирає найкраще. Насправді їм не потрібно їх називати, оскільки з типу результату він знає, що вони вже дадуть. Фактичний дзвінок робиться, коли з’ясується, що було остаточно вибрано.
Йоханнес Шауб - ліб

26

Приховані функції:

  1. Чисті віртуальні функції можуть мати реалізацію. Поширений приклад, чистий віртуальний деструктор.
  2. Якщо функція кидає виняток, не вказаний у специфікаціях винятків, але функція має std::bad_exceptionв своєму винятку специфікацію, виняток перетворюється на std::bad_exceptionта викидається автоматично. Таким чином ви принаймні дізнаєтесь, що bad_exceptionкинуто. Детальніше читайте тут .

  3. функція спробуйте блоки

  4. Ключове слово шаблону в розрізненні typedefs у шаблоні класу. Якщо ім'я спеціалізації шаблону члена з’являється після a ., ->або ::оператора, і це ім’я має чітко визначені параметри шаблону, приставте ім'я шаблону члена до шаблону ключового слова. Детальніше читайте тут .

  5. параметри за замовчуванням функції можуть бути змінені під час виконання. Детальніше читайте тут .

  6. A[i] працює так само добре i[A]

  7. Тимчасові екземпляри класу можна змінювати! Функція "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)
    }

P Тато: A [i] == * (A + i) == * (i + A) == i [A]
abelenky

Я отримую комутацію, це просто те, що це означає, що [] не має власного семантичного значення і просто еквівалентний заміні макро-стилю, де "x [y]" замінено на "(* ((x) + (y ))) ". Зовсім не те, що я очікував. Цікаво, чому це визначено саме так.
P тато

Зворотна сумісність з 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
David Rodríguez - dribeas

2
+1 для Ніка, люди можуть звести з розуму, якщо про це не знають .find().
LiraNuna

або " const map::operator[]генерує повідомлення про помилки"
просто хтось

2
Це не особливість мови, це особливість бібліотеки стандартних шаблонів. Це також досить очевидно, оскільки оператор [] повертає дійсну посилання.
Рамон Заразуа Б.

2
Мені довелося використовувати карти в C #, де карти не так поводяться, щоб зрозуміти, що це особливість. Я думав, що мене це дратувало більше, ніж я використовував, але, здається, я помилявся. Мені це не вистачає в C #.
sbi

20

Якщо розміщення функцій або змінних у безіменному просторі імен знижує використання staticобмеження на область файлу.


"застарілий" - сильний термін ...
Potatoswatter

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

@GMan: жодних проблем, я не думаю, що сторінки SO так "вмирають". Як раз для обох сторін історії, staticглобальна сфера ні в якому разі не заставляється. (Довідково: C ++ 03 §D.2)
Potatoswatter

А при ближчому читанні "Ім'я, оголошене у глобальному просторі імен, має глобальну область простору імен (також його називають глобальною сферою дії)". Це насправді це означає?
Potatoswatter

@Potato: Так. :) 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 ++: Повне керівництво / Розділ 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
Або: вектор <string> V ((istream_iterator <string> (cin)), istream_iterator <string>);
UncleBens

5
ви маєте на увазі vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());- відсутні дужки після другого
параграму

1
Це насправді не прихована функція C ++. Більше функції STL. STL! = Мова
Нік Бедфорд,

14

Ви можете шаблонувати біт-поля.

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

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


1
Дивіться тут, де я недавно запропонував його для п-бітної арифметики: stackoverflow.com/questions/8309538 / ...
sehe

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

Контроль: Я не думаю. Обидва в кінцевому підсумку викликають конструктор копій, навіть незважаючи на те, що перший ЛОКУЄ, як оператор присвоєння, це справді копіювальний конструктор.
абеленький

1
Якщо u - інший тип від SomeType, то перший викличе спочатку конструктор перетворення, а потім конструктор копіювання, тоді як другий викличе лише конструктор перетворення.
Затемнення

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

Правда - перший рядок не буде працювати, якщо конструктор буде оголошено явним.
Затемнення

12

Позбавлення попередніх декларацій:

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

Запис операторів переключення за допомогою?: Операторів:

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

Робити все в одному рядку:

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

Нульові структури без запам'ятовування:

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;
Зіфре

2
Цей синтаксис "float c = (a (), b (), 1.0f);" корисний для акцентування операції по призначенню (присвоєння "с"). Операції присвоєння мають важливе значення в програмуванні, оскільки вони рідше стають застарілими ІМО. Не знаю, чому, можливо, щось пов'язане з функціональним програмуванням, коли стан програми повторно присвоюється кожному кадру. PS. І ні, "int d = (11,22,1.0f)" буде дорівнює "1". Тестували хвилину тому на VS2008.
AareP

2
+1 Не варто вам дзвонити main ? Я хотів би запропонувати global().main();і просто забути про одноточечного ( ви можете просто працювати з тимчасовим, який отримує її термін служби продовжений )
sehe

1
Я сумніваюсь у призначенні посилань портативно. Я люблю структуру відмовлятися від декларацій вперед.
Томас Едінг

12

Потрійний умовний оператор ?:вимагає, щоб його другий і третій операнди мали "приємні" типи (говорять неофіційно). Але ця вимога має один виняток (призначений каламбур): або другий, або третій операнд може бути виразом кидка (який має типvoid ), незалежно від типу іншого операнда.

Іншими словами, за допомогою ?:оператора можна записати наступні виразно виражені C ++ вирази

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

До речі, той факт, що викид виразів є насправді виразом (типу 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(); } };

Я використовував це для впровадження підтримки вирівнювання, яка автоматично визначає найсуворіше вирівнювання за допомогою правила домінування.

Це стосується не лише віртуальних функцій, але і для іменdedef, статичних / невіртуальних членів та будь-чого іншого. Я бачив, як він використовувався для впровадження перезаписуваних рис у метапрограмах.


1
Акуратний. Якась конкретна причина, яку ви включили struct Cу свій приклад ...? Ура.
Тоні Делрой
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.