Немає C ++ любові, коли мова йде про "приховані риси" рядків питань? Подумав, що я викину його туди. Які деякі приховані особливості C ++?
Немає C ++ любові, коли мова йде про "приховані риси" рядків питань? Подумав, що я викину його туди. Які деякі приховані особливості C ++?
Відповіді:
Більшість програмістів на C ++ знайомі з потрійним оператором:
x = (y < 0) ? 10 : 20;
Однак вони не усвідомлюють, що це може бути використане як значення:
(a == 0 ? a : b) = 1;
який є скороченим
if (a == 0)
a = 1;
else
b = 1;
Використовуйте обережно :-)
(value ? function1 : function2)()
.
function1
і function2
неявно перетворюються на покажчики функцій, а результат неявно перетворюється назад.
Ви можете помістити URI в джерело C ++ без помилок. Наприклад:
void foo() {
http://stackoverflow.com/
int bar = 4;
...
}
goto
якою є C ++). Все, що слідує за двома нахилами, - це коментар. Отже, з http://stackoverflow.com
, http
є міткою (теоретично ви могли б написати goto http;
), і //stackoverflow.com
є лише коментарем в кінці рядка. Обидва вони є легальними C ++, тому конструкція складається. Звичайно, це не робить нічого нечітко корисного.
goto http;
насправді не відповідає URL-адресі. :(
Арифметика вказівника.
Програмісти на C ++ вважають за краще уникати покажчиків через помилки, які можна ввести.
Найкрутіший C ++, який я коли-небудь бачив? Аналогові літерали.
Я погоджуюсь з більшістю публікацій там: 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 )
Однією з мовних особливостей, яку я вважаю дещо прихованою, оскільки я ніколи не чув про це протягом усього свого навчання в школі, - псевдонім простору імен. Це не було мені до відома, поки я не зіткнувся з прикладами цього в документації щодо підвищення. Звичайно, тепер, коли я знаю про нього, ви можете знайти його в будь-якій стандартній довідці C ++.
namespace fs = boost::filesystem;
fs::path myPath( strPath, fs::native );
using
.
В ініціальній частині for
циклу можуть бути оголошені не тільки змінні , але й класи та функції.
for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
...
}
Це дозволяє мати декілька змінних різних типів.
Оператор масиву асоціативний.
A [8] є синонімом * (A + 8). Оскільки додавання є асоціативним, його можна переписати як * (8 + A), що є синонімом для ..... 8 [A]
Ти не сказав корисного ... :-)
A
взагалі не має значення. Наприклад, якби A
було char*
, код все-таки був би дійсним.
Мало відомо, що профспілки можуть бути і шаблонами:
template<typename From, typename To>
union union_cast {
From from;
To to;
union_cast(From from)
:from(from) { }
To getTo() const { return to; }
};
І вони також можуть мати конструктори та функції членів. Тільки нічого, що має відношення до спадкування (включаючи віртуальні функції).
From
і To
встановлено та використовується відповідно. Такий союз може бути використаний з визначеною поведінкою (хоча To
це масив неподписаних знаків або структура, що спільно використовує початкову послідовність From
). Навіть якщо ви використовуєте його не визначеним способом, він все ще може бути корисним для роботи низького рівня. У будь-якому випадку, це лише один приклад шаблону об'єднання - для шаблонного об'єднання можуть бути й інші способи використання.
C ++ - це стандарт, прихованих функцій не повинно бути ...
C ++ - це багатопарадигмальна мова, ви можете зробити ставку на свої останні гроші на те, щоб там були приховані функції. Один із багатьох прикладів: метапрограмування шаблонів . Ніхто з комітету зі стандартів не мав наміру існувати підмовою, повністю завершеною Тьюрінгом, яка виконується під час компіляції.
Ще одна прихована функція, яка не працює в 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
}
Про те, що мало хто знає, тривалість життя тимчасових посилань, на які посилаються const-посилання. Або, принаймні, це моя улюблена частина знання C ++, про яку більшість людей не знає.
const MyClass& x = MyClass(); // temporary exists as long as x is in scope
Приємною особливістю, яка часто не використовується, є блок-пробний блок на всій функції:
int Function()
try
{
// do something here
return 42;
}
catch(...)
{
return -1;
}
Основним використанням буде переклад виключення до іншого класу винятків та повторне скидання або переклад між винятками та обробкою коду помилок на основі повернення.
return
зловити блок функцій Спробуйте, лише перезавантажте.
Багато хто знає про 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;
Досить прихованою особливістю є те, що ви можете визначити змінні в умові 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()) {
// ...
}
(а також в умові). Але я не надто впевнений, чи всі вони такі корисні :)
if((a = f()) == b) ...
, але ця відповідь фактично оголошує змінну в умові.
for(...; int i = foo(); ) ...;
це буде проходити через тіло, доки i
це правда, ініціалізуючи його кожного разу знову. Цикл, який ви показуєте, просто демонструє змінну декларацію, але не змінну декларацію, яка одночасно виступає умовою :)
Іноді ви дійсно використовуєте оператор комами, але хочете переконатись, що жоден визначений користувачем оператор кома не заважає, оскільки, наприклад, ви покладаєтесь на точки послідовності між лівою та правою стороною або хочете переконатися, що нічого не заважає бажаному дії. Ось тут void()
вступає гра:
for(T i, j; can_continue(i, j); ++i, void(), ++j)
do_code(i, j);
Ігноруйте посадочні місця, які я поставив, за умовою та кодом. Важливо те void()
, що змушує компілятор використовувати вбудований оператор кома. Це може бути корисно при впровадженні класів рис, іноді теж.
Ініціалізація масиву в конструкторі. Наприклад, у класі, якщо у нас є масив int
як:
class clName
{
clName();
int a[10];
};
Ми можемо ініціалізувати всі елементи масиву до його за замовчуванням (тут усі елементи масиву до нуля) у конструкторі:
clName::clName() : a()
{
}
Ой, натомість я можу скласти список ненавистей домашніх тварин:
З плюсу
Ви можете отримати доступ до захищених даних та членів функцій будь-якого класу без визначеної поведінки та з очікуваною семантикою. Читайте далі, щоб побачити як. Прочитайте також звіт про дефекти про з цього приводу.
Зазвичай 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);
}
Ще одна прихована особливість полягає в тому, що ви можете викликати об’єкти класу, які можна перетворити на функціональні покажчики або посилання. Розв’язання перевантажень робиться за їх результатом, і аргументи ідеально передаються.
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
}
Вони називаються "сурогатними функціями виклику".
Приховані функції:
Якщо функція кидає виняток, не вказаний у специфікаціях винятків, але функція має std::bad_exception
в своєму винятку специфікацію, виняток перетворюється на std::bad_exception
та викидається автоматично. Таким чином ви принаймні дізнаєтесь, що bad_exception
кинуто. Детальніше читайте тут .
функція спробуйте блоки
Ключове слово шаблону в розрізненні typedefs у шаблоні класу. Якщо ім'я спеціалізації шаблону члена з’являється після a .
, ->
або ::
оператора, і це ім’я має чітко визначені параметри шаблону, приставте ім'я шаблону члена до шаблону ключового слова. Детальніше читайте тут .
параметри за замовчуванням функції можуть бути змінені під час виконання. Детальніше читайте тут .
A[i]
працює так само добре i[A]
Тимчасові екземпляри класу можна змінювати! Функція "non-const member" може бути викликана тимчасовим об'єктом. Наприклад:
struct Bar {
void modify() {}
}
int main (void) {
Bar().modify(); /* non-const function invoked on a temporary. */
}
Детальніше читайте тут .
Якщо до і після операції :
потрійного ( ?:
) оператора присутні два різних типи , то результуючий тип виразу є тим, який є найбільш загальним з двох. Наприклад:
void foo (int) {}
void foo (double) {}
struct X {
X (double d = 0.0) {}
};
void foo (X) {}
int main(void) {
int i = 1;
foo(i ? 0 : 0.0); // calls foo(double)
X x;
foo(i ? 0.0 : x); // calls foo(X)
}
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 ++ цього не знають.
.find()
.
const map::operator[]
генерує повідомлення про помилки"
Якщо розміщення функцій або змінних у безіменному просторі імен знижує використання static
обмеження на область файлу.
static
глобальна сфера ні в якому разі не заставляється. (Довідково: C ++ 03 §D.2)
static
використання слід використовувати лише в межах типу класу або функції.
Визначення функцій звичайних друзів у шаблонах класу потребує особливої уваги:
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
Маловідомий, але наступний код чудово
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; !
};
Прочитайте файл у вектор рядків:
vector<string> V;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(V));
vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());
- відсутні дужки після другого
Ви можете шаблонувати біт-поля.
template <size_t X, size_t Y>
struct bitfield
{
char left : X;
char right : Y;
};
Я ще не придумав для цього будь-якої мети, але це, звичайно, здивувало мене.
Одна з найцікавіших граматик будь-яких мов програмування.
Три з цих речей належать разом, а дві - щось зовсім інше ...
SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));
Усі, окрім третього та п’ятого, визначають SomeType
об’єкт у стеку та ініціалізують його ( u
у першому випадку та конструкторі за замовчуванням у четвертому. Третій оголошує функцію, яка не приймає жодних параметрів, та повертає a SomeType
. П'ятий аналогічно оголошує функція , яка приймає один параметр за значенням типу SomeType
імені u
.
Позбавлення попередніх декларацій:
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;
FStruct s = {};
ще коротше.
main
? Я хотів би запропонувати global().main();
і просто забути про одноточечного ( ви можете просто працювати з тимчасовим, який отримує її термін служби продовжений )
Потрійний умовний оператор ?:
вимагає, щоб його другий і третій операнди мали "приємні" типи (говорять неофіційно). Але ця вимога має один виняток (призначений каламбур): або другий, або третій операнд може бути виразом кидка (який має типvoid
), незалежно від типу іншого операнда.
Іншими словами, за допомогою ?:
оператора можна записати наступні виразно виражені C ++ вирази
i = a > b ? a : throw something();
До речі, той факт, що викид виразів є насправді виразом (типу void
), а не висловом, - ще одна маловідома особливість мови C ++. Це означає, серед іншого, що наступний код є абсолютно дійсним
void foo()
{
return throw something();
}
хоча не так багато сенсу робити це таким чином (можливо, в якомусь загальному коді шаблону це може стати в нагоді).
Правило домінування корисне, але мало відомо. Це говорить про те, що навіть якщо в не унікальному шляху через решітку базового класу, пошук імен для частково прихованого члена є унікальним, якщо член належить до віртуального базового класу:
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, статичних / невіртуальних членів та будь-чого іншого. Я бачив, як він використовувався для впровадження перезаписуваних рис у метапрограмах.
struct C
у свій приклад ...? Ура.