Нет любви C ++, когда речь идет о «скрытых особенностях» ряда вопросов? Подумал, что выкину его там. Какие скрытые возможности C ++?
Нет любви C ++, когда речь идет о «скрытых особенностях» ряда вопросов? Подумал, что выкину его там. Какие скрытые возможности C ++?
Ответы:
Большинство программистов на C ++ знакомы с тернарным оператором:
x = (y < 0) ? 10 : 20;
Однако они не понимают, что его можно использовать как lvalue:
(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 ++ для создания альтернативных стилей кодирования, которые не должны находиться в списке «официальных парадигм C ++».
Используя шаблоны , вы можете создать код, который будет работать с большинством типов, включая не тот, который вы думали вначале. Вы также можете повысить безопасность типов (например, автоматический тип malloc / realloc / free). Возможности объектов C ++ действительно эффективны (и, следовательно, опасны при неосторожном использовании), но даже у динамического полиморфизма есть статическая версия в C ++: CRTP .
Я обнаружил, что большинство книг типа " Эффективный C ++ " от Скотта Мейерса или книг типа " Исключительный C ++ " от Херба Саттера одновременно удобны для чтения и представляют собой кладезь информации об известных и менее известных особенностях 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
были 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
}
Время жизни временных файлов, привязанных к константным ссылкам, мало кому известно. Или, по крайней мере, это моя любимая часть знаний о C ++, о которой большинство людей не знает.
const MyClass& x = MyClass(); // temporary exists as long as x is in scope
Приятной особенностью, которая используется нечасто, является блок try-catch для всей функции:
int Function()
try
{
// do something here
return 42;
}
catch(...)
{
return -1;
}
Основное использование будет заключаться в преобразовании исключения в другой класс исключения и повторном вызове или в преобразовании между исключениями и обработкой кода ошибки на основе возврата.
return
поймать блок Function Try, только перебросить.
Многие знают о метафункции 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 и его блоки 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()) {
// ...
}
(а также в состоянии for). Но я не уверен, насколько они полезны :)
if((a = f()) == b) ...
, но этот ответ фактически объявляет переменную в условии.
for(...; int i = foo(); ) ...;
This будет проходить через тело, пока i
истинно, инициализируя его каждый раз снова. Цикл, который вы показываете, просто демонстрирует объявление переменной, но не объявление переменной, которое одновременно действует как условие :)
Иногда вы правильно используете оператор запятой, но вы хотите, чтобы ни один пользовательский оператор запятой не мешал, потому что, например, вы полагаетесь на точки последовательности между левой и правой стороной или хотите убедиться, что ничто не мешает желаемому действие. Вот где void()
вступает в игру:
for(T i, j; can_continue(i, j); ++i, void(), ++j)
do_code(i, j);
Игнорируйте заполнители, которые я поставил для условия и кода. Важно то void()
, что заставляет компилятор использовать встроенный оператор запятой. Иногда это может быть полезно при реализации классов свойств.
Инициализация массива в конструкторе. Например, в классе, если у нас есть массив int
as:
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);
}
Это будет еще проще с объявлением using в производном классе, которое делает имя члена общедоступным и ссылается на член базового класса.
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
был брошен. Подробнее читайте здесь .
функциональные блоки try
Ключевое слово template для устранения неоднозначности typedef в шаблоне класса. Если имя шаблон члена специализации появляется после .
, ->
или ::
оператора, и это имя имеет явно квалифицированные параметры шаблона, префикс имя шаблона члена с ключевыми словами шаблона. Подробнее читайте здесь .
Значения по умолчанию для параметров функции могут быть изменены во время выполнения. Подробнее читайте здесь .
A[i]
работает так же хорошо, как i[A]
Временные экземпляры класса можно изменять! Неконстантная функция-член может быть вызвана для временного объекта. Например:
struct Bar {
void modify() {}
}
int main (void) {
Bar().modify(); /* non-const function invoked on a temporary. */
}
Подробнее читайте здесь .
Если два разных типа присутствуют до и после выражения оператора :
ternary ( ?:
), то результирующий тип выражения является наиболее общим из двух. Например:
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 ++ Templates: The Complete Guide / Section 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;
Написание операторов 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;
FStruct s = {};
еще короче.
main
? Я бы предложил global().main();
и просто забыл о синглтоне ( вы можете просто работать с временным, что продлевает его срок службы )
Тернарный условный оператор ?:
требует, чтобы его второй и третий операнды имели "приемлемые" типы (говоря неформально). Но это требование имеет одно исключение (каламбур): второй или третий операнд может быть выражением throw (имеющим типvoid
), независимо от типа другого операнда.
Другими словами, можно написать следующие действительно корректные выражения C ++, используя ?:
оператор
i = a > b ? a : throw something();
Кстати, тот факт, что выражение throw на самом деле является выражением (типа 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(); } };
Я использовал это, чтобы реализовать поддержку выравнивания, которая автоматически определяет самое строгое выравнивание с помощью правила доминирования.
Это относится не только к виртуальным функциям, но и к именам typedef, статическим / не виртуальным членам и всему остальному. Я видел, как это использовалось для реализации перезаписываемых свойств в метапрограммах.
struct C
в свой пример ...? Приветствия.