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

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

Ответы

Ответ 1

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

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

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

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

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

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

Следует использовать с осторожностью: -)

Ответ 2

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

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

    ...
}

Ответ 3

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

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

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

Ответ 4

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

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

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

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

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

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

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

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

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

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

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

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

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

(Источник: среди прочих, Herb Sutter онлайн Guru of the Week # 84: http://www.gotw.ca/gotw/084.htm)

Ответ 5

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

namespace fs = boost::filesystem;

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

Ответ 6

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

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

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

Ответ 7

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

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

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

Ответ 8

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

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

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

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

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

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

Ответ 12

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

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

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

Ответ 13

Многие знают метафайлы 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;

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

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

Ответ 14

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

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

Ответ 15

Предотвращение переадресации оператора запятой

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

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

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

Ответ 16

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

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

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

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

Ответ 17

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

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

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

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

Ответ 18

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

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

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 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 inspect the stack 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 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 inspect the stack middle elements!
    std::deque<int> &d = pillager::get(s);
}

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

void f(std::stack<int> &s) {
    // now, let decide to mess with that stack!
    struct pillager : std::stack<int> {
        using std::stack<int>::c;
    };

    // haha, now let inspect the stack middle elements!
    std::deque<int> &d = s.*(&pillager::c);
}

Ответ 19

Скрытые функции:

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

  • функции try blocks

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

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

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

Ответ 20

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

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
}

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

Ответ 21

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

Ответ 22

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

Ответ 23

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

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

Отказ от ответственности: я вставлял этот раздел из С++ Templates: Полное руководство/Раздел 8.4

Ответ 24

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

Немного известно, но следующий код в порядке

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

Как и следующая странная выглядящая

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

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

Ответ 25

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

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

istream_iterator

Ответ 26

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

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

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

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

Ответ 27

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

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

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

Ответ 28

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

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

Обнуление структур без 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;

Ответ 29

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

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

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

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

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

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

Ответ 30

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

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