it-swarm-ru.tech

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

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

114
Craig H

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

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

Тем не менее, они не понимают, что это может быть использовано в качестве lvalue:

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

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

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

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

308
Ferruccio

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

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

    ...
}
238
Ben

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

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

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

140
Anonymouse

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

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

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

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

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

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

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

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

Я обнаружил, что большинство книг типа " Effective C++ " от Скотта Мейерса или книги типа " Exceptional C++ " от Херба Саттера быть как легко читаемым, так и довольно сокровищем информации об известных и менее известных функциях C++.

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

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

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

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

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

119
paercebal

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

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );
118
Jason Mock

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

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

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

102
Johannes Schaub - litb

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

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

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

77
Colin Jensen

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

template<typename From, typename To>
union union_cast {
    From from;
    To   to;

    union_cast(From from)
        :from(from) { }

    To getTo() const { return to; }
};

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

73
Johannes Schaub - litb

C++ - это стандарт, не должно быть никаких скрытых возможностей ...

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

72
Konrad Rudolph

Еще одна скрытая возможность, которая не работает в 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
}
66
Johannes Schaub - litb

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

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

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

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

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

52
vividos

Многие знают о метафункции 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; };
44
Johannes Schaub - litb

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

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

43
Johannes Schaub - litb

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

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

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

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

29
Johannes Schaub - litb

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

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

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

clName::clName() : a()
{
}
28
Poorna

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

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

На положительной стороне

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

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

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

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

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

  3. блоки функций

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

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

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

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

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

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

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
}

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

26
Johannes Schaub - litb

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++ программистов не знают этого.

24
Constantin

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

20
Jim Hunziker

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

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

19
Özgür

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

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

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

А также следующий странно выглядящий

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

Зная об этом, вы можете воспользоваться в некоторых областях. Один пример: функции void не могут возвращать значение, но вы также не можете просто ничего не возвращать, потому что они могут быть созданы с помощью non-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; !
};
18
Johannes Schaub - litb

Прочитать файл в вектор строк:

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

istream_iterator

17
Jason Baker

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

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

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

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

14
Eclipse

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

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

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

14
Kaz Dragon

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

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

12
Johannes Schaub - litb

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

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

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

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

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

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

12
AnT

Избавляемся от предварительных деклараций:

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;
12
AareP

Я обнаружил, что этот блог - удивительный ресурс о тайнах C++: C++ Truths .

9
Drealmer

Опасный секрет

Fred* f = new(ram) Fred(); http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10
f->~Fred();

Мой любимый секрет, который я редко вижу:

class A
{
};

struct B
{
  A a;
  operator A&() { return a; }
};

void func(A a) { }

int main()
{
  A a, c;
  B b;
  a=c;
  func(b); //yeah baby
  a=b; //gotta love this
}
8
user34537

Местные классы потрясающие:

struct MyAwesomeAbstractClass
{ ... };


template <typename T>
MyAwesomeAbstractClass*
create_awesome(T param)
{
    struct ans : MyAwesomeAbstractClass
    {
        // Make the implementation depend on T
    };

    return new ans(...);
}

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

8
Alexandre C.

Одна скрытая возможность, даже скрытая для разработчики GCC , заключается в инициализации члена массива с использованием строкового литерала. Предположим, у вас есть структура, которая должна работать с массивом C, и вы хотите инициализировать элемент массива содержимым по умолчанию

struct Person {
  char name[255];
  Person():name("???") { }
};

Это работает и работает только с массивами символов и строковыми литералами. Нет strcpy не требуется!

7
Johannes Schaub - litb

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

Шаблонное метапрограммирование вряд ли является скрытой функцией. Это даже в библиотеке буста. Смотрите MPL . Но если "почти скрытый" достаточно хорош, взгляните на повысить библиотеки . Он содержит много вкусностей, которые нелегко получить без поддержки сильной библиотеки.

Одним из примеров является boost.lambda библиотека, которая интересна тем, что C++ не имеет лямбда-функций в текущем стандарте.

Другим примером является Loki , который "широко использует метапрограммирование шаблонов C++ и реализует несколько часто используемых инструментов: список типов, функтор, singleton, интеллектуальный указатель, фабрику объектов, посетитель и мультиметоды". [ Википедия ]

6
Markowitch

Здесь нет скрытых возможностей, но язык C++ очень мощный, и часто даже разработчики стандартов не могут представить, для чего можно использовать C++.

На самом деле из достаточно простой языковой конструкции вы можете написать что-то очень мощное. Многие такие вещи доступны на сайте www.boost.org в качестве примеров (и среди них http://www.boost.org/doc/libs/1_36_0/doc/html/lambda.html ).

Чтобы понять, как простая языковая конструкция может быть объединена с чем-то мощным, полезно прочитать "Шаблоны C++: Полное руководство" Дэвида Вандевурда, Николая М. Йосуттиса и действительно волшебную книгу "Современный дизайн C++ ..." Андрея Александреску .

И, наконец, трудно изучать C++, вы должны попытаться заполнить его;)

5
sergtk

Мне кажется, что мало кто знает о безымянных пространствах имен:

namespace {
  // Classes, functions, and objects here.
}

Безымянные пространства имен ведут себя так, как будто они были заменены:

namespace __unique_{ /* empty body */ }
using namespace __unique_name__;
namespace __unique_{
  // original namespace body
}

"… где все вхождения [этого уникального имени] в единице перевода заменены одним и тем же идентификатором, и этот идентификатор отличается от всех других идентификаторов во всей программе". [C++ 03, 7.3.1.1/1]

4
vobject
4
Özgür

Я не уверен насчет скрытого, но есть некоторые интересные"хитрости" , которые, вероятно, не очевидны из простого чтения спецификации.

3
dbrien

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

3
ugasoft

Большинство разработчиков C++ игнорируют мощь шаблонного метапрограммирования. Проверьте Локи Либари . Он реализует несколько расширенных инструментов, таких как список типов, функтор, синглтон, интеллектуальный указатель, фабрика объектов, посетитель и мультиметоды, широко использующие метапрограммирование шаблонов (из wikipedia ). По большей части вы можете рассматривать их как "скрытую" функцию C++.

3
Sridhar Iyer

От C++ Truths .

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

template<class T> // (a) a base template
void f(T) {
  std::cout << "f(T)\n";
}

template<>
void f<>(int*) { // (b) an explicit specialization
  std::cout << "f(int *) specilization\n";
}

template<class T> // (c) another, overloads (a)
void f(T*) {
  std::cout << "f(T *)\n";
}

template<>
void f<>(int*) { // (d) another identical explicit specialization
  std::cout << "f(int *) another specilization\n";
}
3
Özgür
  • указатели на методы класса
  • Ключевое слово "typename"
3
shoosh
3
sdcvvc

main () не требует возвращаемого значения:

int main(){}

самая короткая действительная программа на C++.

2
Jeffrey Faust

Обратите внимание на разницу между указателем свободной функции и указателем на функцию-член:

функция-член:

struct S
{
 void func(){};
};
int main(){
void (S::*pmf)()=&S::func;//  & is mandatory
}

и бесплатная функция:

void func(int){}
int main(){
void (*pf)(int)=func; // & is unnecessary it can be &func as well; 
}

Благодаря этому избыточному &, вы можете добавить потоковые манипуляторы - которые являются бесплатными функциями - в цепочке без него:

cout<<hex<<56; //otherwise you would have to write cout<<&hex<<56, not neat.
2
Özgür
  1. map::insert(std::pair(key, value)); не перезаписывает, если значение ключа уже существует.

  2. Вы можете создать экземпляр класса сразу после его определения: (Могу добавить, что эта функция дала мне сотни ошибок компиляции из-за пропущенной точки с запятой, и я никогда не видел, чтобы кто-нибудь использовал это в классах)

    class MyClass {public: /* code */} myClass;
    
2
Viktor Sehr

Есть тонна "хитрых" конструкций в C++. Они идут от "простых" реализаций запечатанные/окончательные классы с использованием виртуального наследования. И попадаем в довольно "сложные" конструкции метапрограммирования, такие как Boost MPL ( tutorial ). Возможности для стрельбы себе в ногу бесконечны, но если держать их под контролем (то есть опытных программистов), обеспечьте лучшую гибкость с точки зрения удобства обслуживания и производительности.

1
Amir

Класс и структура class-ключи почти идентичны. Основное различие заключается в том, что классы по умолчанию имеют закрытый доступ для членов и баз, а структуры по умолчанию имеют открытый доступ:

// this is completely valid C++:
class A;
struct A { virtual ~A() = 0; };
class B : public A { public: virtual ~B(); };

// means the exact same as:
struct A;
class A { public: virtual ~A() = 0; };
struct B : A { virtual ~B(); };

// you can't even tell the difference from other code whether 'struct'
// or 'class' was used for A and B

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

1
a_m0d

идиома косвенного преобразования :

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

template <class T>
class Ptr
{
public:
 operator bool() const
 {
  return (rawptr ? true: false);
 }
//..more stuff
private:
 T * rawptr;
};

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

Ptr<int> ptr(new int);
if(ptr ) //calls operator bool()
 cout<<"int value is: "<<*ptr <<endl;
else
 cout<<"empty"<<endl;

Кроме того, неявное преобразование в bool требуется в условных объявлениях, таких как:

if (shared_ptr<X> px = dynamic_pointer_cast<X>(py))
{
 //we get here only of px isn't empty
} 

Увы, это автоматическое преобразование открывает путь к нежелательным сюрпризам:

Ptr <int> p1;
Ptr <double> p2;

//surprise #1
cout<<"p1 + p2 = "<< p1+p2 <<endl; 
//prints 0, 1, or 2, although there isn't an overloaded operator+()

Ptr <File> pf;
Ptr <Query> pq; // Query and File are unrelated 

//surprise #2
if(pf==pq) //compares bool values, not pointers! 

Решение: используйте идиому "косвенного преобразования" путем преобразования указателя на элемент данных [pMember] в bool, чтобы было только 1 неявное преобразование, которое предотвратит вышеупомянутое неожиданное поведение: pMember-> bool, а не bool-> что-то остальное.

1
Özgür

Если оператор delete () принимает аргумент размера в дополнение к * void, это означает, что он будет базовым классом. Этот аргумент размера делает возможным проверку размера типов для уничтожения правильного. Вот что Стивен Дьюхерст говорит об этом:

Также обратите внимание, что мы использовали версию оператора delete с двумя аргументами, а не обычную версию с одним аргументом. Эта версия с двумя аргументами является другой "обычной" версией оператора удаления члена, часто используемого базовыми классами, которые ожидают, что производные классы наследуют свою реализацию удаления оператора. Второй аргумент будет содержать размер удаляемого объекта - информацию, которая часто полезна при реализации пользовательского управления памятью.

1
Özgür

Я нахожу рекурсивные шаблоны довольно крутыми:

template<class int>
class foo;

template
class foo<0> {
    int* get<0>() { return array; }
    int* array;  
};

template<class int>
class foo<i> : public foo<i-1> {
    int* get<i>() { return array + 1; }  
};

Я использовал это для генерации класса с 10-15 функциями, которые возвращают указатели на различные части массива, поскольку API, который я использовал, требовал один указатель на функцию для каждого значения.

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

1
Macke

Мое любимое (на данный момент) отсутствие семантики в таком утверждении, как A = B = C. Какое значение A в основном не определено.

Подумай об этом:

class clC
{
public:
   clC& operator=(const clC& other)
   {
      //do some assignment stuff
      return copy(other);
   }
   virtual clC& copy(const clC& other);
}

class clB : public clC
{
public:
  clB() : m_copy()
  {
  }

  clC& copy(const clC& other)
  {
    return m_copy;
  }

private:
  class clInnerB : public clC
  {
  }
  clInnerB m_copy;
}

теперь A может иметь тип, недоступный любому другому объекту типа clB, и иметь значение, не связанное с C.

0
Rune FS

Добавление ограничения в шаблоны.

0
Özgür

Указатели членов и оператор указателей членов -> *

#include <stdio.h>
struct A { int d; int e() { return d; } };
int main() {
    A* a = new A();
    a->d = 8;
    printf("%d %d\n", a ->* &A::d, (a ->* &A::e)() );
    return 0;
}

Для методов (a -> * & A :: e) () это немного похоже на Function.call () из javascript

var f = A.e
f.call(a) 

Для участников это немного похоже на доступ с оператором []

a['d']
0
Kamil Szot

Вы можете просмотреть все предопределенные макросы через ключи командной строки с некоторыми компиляторами. Это работает с gcc и icc (компилятор Intel C++):

$ touch empty.cpp
$ g++ -E -dM empty.cpp | sort >gxx-macros.txt
$ icc -E -dM empty.cpp | sort >icx-macros.txt
$ touch empty.c
$ gcc -E -dM empty.c | sort >gcc-macros.txt
$ icc -E -dM empty.c | sort >icc-macros.txt

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

Сравнить (после сортировки):

 $ diff gxx-macros.txt icx-macros.txt
 $ diff gxx-macros.txt gcc-macros.txt
 $ diff icx-macros.txt icc-macros.txt
0
Roger Pate
class Empty {};

namespace std {
  // #1 specializing from std namespace is okay under certain circumstances
  template<>
  void swap<Empty>(Empty&, Empty&) {} 
}

/* #2 The following function has no arguments. 
   There is no 'unknown argument list' as we do
   in C.
*/
void my_function() { 
  cout << "whoa! an error\n"; // #3 using can be scoped, as it is in main below
  // and this doesn't affect things outside of that scope
}

int main() {
  using namespace std; /* #4 you can use using in function scopes */
  cout << sizeof(Empty) << "\n"; /* #5 sizeof(Empty) is never 0 */
  /* #6 falling off of main without an explicit return means "return 0;" */
}
0
dirkgently