После обсуждения с новым разработчиком в моей команде я понял, что в C++ все еще есть привычки использовать конструкции C, потому что они должны быть лучше (то есть быстрее, компактнее, красивее, выбирайте свою причину).
Какими примерами стоит поделиться, показывая конструкции C, по сравнению с аналогичной конструкцией C++?
Для каждого примера мне нужно прочитать причины, по которым конструкция C++ так же хороша или даже лучше исходной конструкции C. Цель состоит в том, чтобы предложить альтернативы некоторым конструкциям C, которые считаются в некоторой степени опасными / небезопасными в коде C++ (принимаются только допустимые ответы C++ 0x, если они четко обозначены как C++ 0x).
Я опубликую ниже ответ (встроенная инициализация структуры) в качестве примера.
Примечание 1: Пожалуйста, укажите один ответ на каждый случай. Если у вас несколько дел, опубликуйте несколько ответов
Примечание 2: Это не вопрос C. Не добавляйте к этому вопросу тег "C".Это не должно стать борьбой между C++ и C. Только изучение некоторых конструкций подмножества C C++ и их альтернативы в других "инструментах" C++
Примечание 3: это не вопрос C-bash. Мне нужны причины. Хвастовство, трепа и недоказанные сравнения будут понижены. Упоминание функций C++ без эквивалента C можно считать неуместным: я хочу, чтобы функция C была рядом с функцией C++.
Я согласен с Адамом насчет тега C.
Мои первоначальные причины заключались в том, что я не хотел трепаться на C++ и C. Это не попытка «обратить», и это не попытка похвастаться одним языком против другого. Я просто не хотел «драться» ... Тем не менее, возможно, вы правы.
Если попытки преобразования отсутствуют, почему конструкция C++ априори должна быть «такой же или даже лучше»?
Потому что, когда C лучше и не опасен, он остается хорошей альтернативой для разработчика. Так что меня интересуют только случаи, когда C лучше нет.
Я добавил тег "c" после комментариев Адама и Майка. Спасибо за вклад.
Есть ли у нас «пропагандистский» ярлык? Я больше не буду участвовать в этой теме - игра сфальсифицирована.
@ fizzer.myopenid.com, я не думаю, что он говорит, что C++ лучше всегда. Думаю, он ищет примеры, когда C++ лучше. Я знаю более чем одного разработчика, которому C нравится больше, чем C++, потому что C++ может быть сложнее отлаживать из-за перегрузки оператора и подобных проблем.
@Onorio: Я предполагаю, что произошло недоразумение: несмотря на то, что его комментарий (вероятно, написанный на скорую руку) мог поверить, Физзер внес положительный вклад в обсуждение и показал хорошее понимание C++. Мои собственные знания C++ расширились благодаря нашему обсуждению.
@Onorio: [...] Но вы четко изложили мою точку зрения, вероятно, лучше, чем я в своих сообщениях. Спасибо.
Помечено как опрос, поскольку ожидается несколько ответов, а не один.





Иногда нам нужно в C++ простое агрегирование данных. Поскольку данные в некоторой степени независимы, их защита с помощью инкапсуляции не стоит затраченных усилий.
// C-like code in C++
struct CRect
{
int x ;
int y ;
} ;
void doSomething()
{
CRect r0 ; // uninitialized
CRect r1 = { 25, 40 } ; // vulnerable to some silent struct reordering,
// or adding a parameter
}
; Я вижу три проблемы с приведенным выше кодом:
В приведенном ниже коде конструкторы будут встроены (если они действительно полезны), и, следовательно, будет иметь нулевую стоимость (как код C выше):
// C++
struct CRect
{
CRect() : x(0), y(0) {} ;
CRect(int X, int Y) : x(X), y(Y) {} ;
int x ;
int y ;
} ;
void doSomething()
{
CRect r0 ;
CRect r1(25, 40) ;
}
(Бонус в том, что мы могли бы добавить методы operator ==, но этот бонус не по теме, поэтому стоит упомянуть, но не стоит в качестве ответа.)
Адам Розенфилд сделал интересный комментарий, который я считаю очень интересным:
C99 допускает именованные инициализаторы: CRect r = {.x = 25, .y = 40}
Это не будет компилироваться на C++. Думаю, это нужно добавить в C++, хотя бы для C-совместимости. Во всяком случае, в C это облегчает проблему, упомянутую в этом ответе.
C99 допускает именованные инициализаторы: CRect r = {.x = 25, .y = 40};
Интересный момент. Это не будет компилироваться в C++ g ++ («ошибка: ожидаемое первичное выражение перед токеном«. »»), Так что это не делает недействительным весь этот пост, но все же очень хороший момент, о котором стоит знать.
Нет, он не компилируется с g ++, но если вы компилируете код чистый C, он работает с gcc. Я тестировал его с помощью gcc 3.4.4, но уверен, что он работает и во многих других версиях gcc.
Я не понимаю, почему встроенная инициализация «уязвима» для добавления или изменения порядка элементов, но вызов конструктора, по-видимому, не «уязвим» для добавления или изменения порядка параметров. Либо интерфейс фиксирован, либо нет, и это относится как к структуре, так и к конструктору.
На самом деле, я дам вам «добавление параметра», поскольку, если вы добавите параметр в конструктор, код перестанет компилироваться, тогда как если вы добавите член в структуру, то остатки будут автоматически начинаться с нуля. Но они одинаково уязвимы для повторного заказа.
@onebyone: возьмите две структуры CRect, скомпилируйте их и посмотрите результат. Затем инвертируйте объявление int x / int y внутри структуры. Вы увидите, что C CRect инвертирован, в то время как C++ CRect по-прежнему дает правильный результат. [...]
[...] «Интерфейс» C++ CRect - это конструктор (пока конструктор не изменился, все в порядке), а интерфейс C CRect - это сама структура (в момент изменения она не будет Работа). Встроенное косвенное обращение C++ с нулевой стоимостью, заданное конструктором, меняет все.
Я согласен с этим сообщением, но хочу отметить, что добавление конструктора в структуру сделает ее больше не типом POD. В C++ 0x этого не происходит.
@michalmocny: Вы правы, но «POD» не является проблемой для C++ (то есть кого это волнует?), если вы не играете с разделяемой памятью между процессами, памятью, выделенной malloc, или другими чисто-C-функциями. В этом посте я упоминал только конструкции в стиле C против конструкций C++ в чистом коде, скомпилированном на C++.
Практически любое использование void*.
Вы должны предложить пример кода C с максимальной безопасностью с использованием void * и пример кода C++ с использованием любого решения, предлагающего такое же решение. Это улучшит ваш пост.
Стиль C:
#define max(x,y) (x) > (y) ? (x) : (y)
Стиль C++
inline template<typename T>
const T& max(const T& x, const T& y)
{
return x > y ? x : y;
}
Причина предпочесть подход C++:
Кроме того, версия C++ не оценивает аргументы дважды. Рассмотрим max (++ i, ++ j): версия C будет увеличивать либо i, либо j дважды, а не один раз.
Возможно, лучшей версией были бы две "максимальные" функции. Один ожидает константные ссылочные параметры и возвращает ссылку на константу максимума, другой ожидает неконстантных ссылочных параметров и возвращает неконстантную ссылку максимума ... Но это не тема ^ _ ^ ... + 1
Макс действительно не так прост, вам действительно стоит прочитать этот ddj.com/cpp/184403774
Правильно - я помню, как читал, когда это вышло - моя версия также делает копию, что, вероятно, нежелательно, и я отредактирую, чтобы исправить.
Версия C неправильная, опасная или и то, и другое. Я добавлю скобки, необходимые для безопасности (r).
У вас все еще неправильная версия C. Также необходимы скобки вокруг всего расширения.
Версия C может быть опасной, но я по-прежнему предпочитаю ее «80 строк исходного кода шаблона (плюс библиотека Loki), которые не будут компилироваться ни с одним доступным компилятором». (краткое изложение ссылки motti)
@Roddy: Решение JohnMcG, дополненное неконстантной версией, более чем достаточно для большинства применений. Меня еще не беспокоили std :: min и std :: max, предлагаемые STL (используйте одну строку "#include <algorithm>"), в то время как макрос "#define max" уже уничтожил идеально размещенный / инкапсулированный код.
Конечно, то, что макрос-версию сложно сделать, является частью проблемы.
Несмотря на то, что макросы имеют «область действия» от #define до #undef или до конца файла. template<> ограничены пространствами имен, классами, функциями и блоками, как и другие имена функций и классов.
Мне потребовалось несколько попыток, но я надеюсь, что теперь все в порядке: codepad.org/R6i0te8h. пытается работать с параметрами const / nonconst, и с их разными типами (находит их общий тип с помощью оператора? :) без локи или чего-то еще. но, может быть, есть ошибка, которую я заметил?
void AddUser(LPCSTR lpcstrName, int iAge, const char *lpcstrAddress);
void AddUserByNameOnly(LPCSTR lpcstrName)
{
AddUser(lpcstrName, -1,NULL);
}
void User::Add(LPCSTR lpcstrName, int iAge=-1, const char *lpcstrAddress=NULL);
Позволяет программисту выражать функцию программы в меньшем количестве строк исходного кода и в более компактной форме. Также позволяет выражать значения по умолчанию для неиспользуемых параметров как можно ближе к тому месту, где они фактически используются. Для вызывающей стороны упрощает интерфейс класса / структуры.
Мне не очень нравится использование перегруженных функций. Да, все они добавляют информацию, но я не думаю, что они должны быть одной перегруженной функцией.
Я переформатировал сообщение, чтобы код был хорошо напечатан. Пожалуйста, Стив, не могли бы вы отредактировать свой пост, чтобы разбить его на два поста и объяснить плюсы / минусы каждого решения? (И, возможно, изменить LPCSTR на const char * для людей, не работающих с Windows?). Я жду, чтобы добавить +1 к каждому посту.
@Thomas: Я согласен, даже если это все еще зависит от контекста. Хотя этот пример можно обсудить, их сравнение с кодом C дает им значимость в текущей теме. Я удивлен, что никто не упомянул "abs" и "fabs" C против возможной функции cpp :: abs.
@paercebal. Спасибо за редактирование и предложения. Я сделал, как вы просили.
Параметры по умолчанию очень полезны, но с ними нужно быть осторожным. Я видел, как ими жестоко злоупотребляли (например, «волшебные» значения по умолчанию, которые резко меняют поведение функции.)
В C:
Resource r;
r = Acquire(...);
... Code that uses r ...
Release(r);
где, например, Resource может быть указателем на память, и Acquire / Release будет выделять / освобождать эту память, или это может быть дескриптор открытого файла, где Acquire / Release откроет / закроет этот файл.
Это создает ряд проблем:
Releaser. Если r приобретается и выпускается в том же объеме, код не документирует это самостоятельно.Resource r и r.Acquire(...), r фактически доступен, несмотря на то, что он не инициализирован. Это источник ошибок.Применяя методологию RAII (Resource Acquisition Is Initialization), в C++ получаем
class ResourceRAII
{
Resource rawResource;
public:
ResourceRAII(...) {rawResource = Acquire(...);}
~ResourceRAII() {Release(rawResource);}
// Functions for manipulating the resource
};
...
{
ResourceRAII r(...);
... Code that uses r ...
}
Версия C++ гарантирует, что вы не забудете освободить ресурс (если вы это сделаете, у вас будет утечка памяти, которую легче обнаружить инструментами отладки). Это вынуждает программиста четко указать, как поток данных ресурса (то есть: если он существует только во время области действия функции, это будет ясно показано построением ResourceRAII в стеке). Нет точки между созданием объекта ресурса и его уничтожением, когда ресурс недействителен.
Это также безопасно!
Я считаю, что в вашем примере вам нужно настроить конструктор и деструктор, чтобы они назывались ResourceRAII () и ~ ResourceRAII (), поскольку это имя класса.
+1, RAII - лучшее изобретение C++ на свете. Я даже не нашел ни одного случая, когда он был бы "чрезмерно использован" (например, одноэлементный паттерн). Это просто лучшая вещь со времен нарезанного хлеба, и если бы его изобрели раньше, может, никому не пришлось бы изобретать сборку мусора ...
В C:
#include <stdio.h>
int main()
{
int num = 42;
printf("%s%d%c", "Hello World\n", num, '\n');
return 0;
}
Строка формата анализируется во время выполнения, что означает, что она небезопасна по типу.
в C++:
#include <iostream>
int main()
{
int num = 42;
std::cout << "Hello World\n" << num << '\n';
}
Типы данных известны во время компиляции, а также меньше вводить, потому что нет необходимости в строке формата.
Вы когда-нибудь профилировали stdio против iostream? Я думаю, вы удивитесь, обнаружив, что stdio значительно быстрее, чем iostream.
Я согласен. Хотя это интересно (sprintf легко злоупотребить), iostreams достаточно медленные (то есть в 5 раз эквивалент stdio, если мне не изменяет память), чтобы Херб Саттер написал об этом статью. [...]
[...] Таким образом, этот ответ выходит за рамки моего требования «Мне нужно прочитать, почему конструкция C++ так же хороша или даже лучше, чем исходная конструкция C».
printf (помимо того, что он быстрее) также намного проще вводить. Все закрывающие и повторно открывающиеся кавычки (для cout) действительно замедляют вас.
Ага. Также существует вопрос о многословности - setw, setprecision и друзья по сравнению с компактными модификаторами ширины поля и точности для% конверсий.
«Меньше для ввода» тоже кажется ложным, учитывая, что в приведенном выше примере код iostream будет иметь точно такое же количество символов, за исключением того, что «return 0» из main был исключен ...
Многословие iostream сводит меня с ума, и я отказываюсь использовать его ни в чем, кроме самых тривиальных. А именно: printf ("0x% 08xn", x); vs std :: cout << std :: hex << std :: setfill ('0') << std :: setw (8) << x << std :: dec << std :: endl;
Я согласен со всеми сообщениями о многословности iostream. Iostreams C++ безопаснее, чем любой аналог C (результат всегда будет Ok, т. Е. Без переполнения буфера и без усеченной строки), но он более подробный и медленный. Думаю, об этом следует упомянуть в ответе.
(sn) printf также лучше для i18n, по крайней мере, с gettext, потому что переводчик получает всю строку, а не только некоторые ее фрагменты. При необходимости он даже может изменить порядок значений.
Должен признать, я всегда находил строки формата довольно запутанными, в первую очередь потому, что я никогда не использовал ввод-вывод в стиле C. Необходимость указывать, печатаю ли я double или int, кажется излишней и подверженной ошибкам, особенно в шаблонах. Что касается проблем с производительностью, я понимаю, что большая часть разрыва в производительности вызвана тем, что люди используют std::endl для символов новой строки, а не просто '\n', а остальное можно исправить с помощью std::cout.sync_with_stdio(false). Смотрите тайминги в ответе на stackoverflow.com/questions/1924530/…
C-стиль:
int **foo = new int*[n];
for (int x = 0; x < n; ++x) foo[x] = new int[m];
// (...)
for (int x = 0; x < n; ++x) delete[] foo[x];
delete[] foo;
C++ - стиль:
std::vector< std::vector<int> > foo(n, std::vector<int>(m));
// (...)
Почему контейнеры STL лучше:
Думаю, в 2D-массиве нет необходимости. 2D-массив для C++ может быть поврежден (т.е. одна строка может быть длиной 5 элементов, а другая - 7). В то время как <code> char a [25] [6] </code> всегда работает нормально и быстро. Тот же пример с одномерным массивом и вектором был бы лучше. Пока у нас не будет N-мерных массивов в C++
Также обратите внимание, что доступ через vector :: operator [] осуществляется так же быстро, как и доступ к массиву C через арифметику указателей или использование [].
P.S .: Когда я пишу «2D-массив для C++ может быть сломан», я пишу о выставленном векторе вектора. Частный вектор вектора в классе «Матрица» не будет жертвой этой проблемы благодаря инкапсуляции. Но, полагаю, это выходит за рамки темы.
#define vs. const
Я все время вижу такой код от разработчиков, которые давно пишут на C:
#define MYBUFSIZE 256
. . .
char somestring[MYBUFSIZE];
и т. д. и т. д.
В C++ это было бы лучше:
const int MYBUFSIZE = 256;
char somestring[MYBUFSIZE];
Конечно, еще лучше для разработчика использовать std :: string вместо массива char, но это отдельная проблема.
Проблем с макросами C очень много - в данном случае проверка типов не является главной проблемой.
Судя по тому, что я видел, это кажется чрезвычайно сложной привычкой для программистов на C, переходящих на C++.
Раньше это было неудобно, потому что компиляторы / компоновщики не позволяли вам помещать «const int X = 100» в файл .h - вам нужно «extern const int X» в заголовке и «const int x = 100;» в файле .c, что было болезненно. Хотя сейчас вроде работает нормально.
@Roddy: Сегодня это нормально для константных выражений (я думаю, они будут встроены)
Мне действительно нравится использовать перечисления для констант - хорошо работает как на C, так и на C++. Единственный недостаток заключается в том, что вы не можете форсировать конкретный тип, но это редко вызывает беспокойство.
Одна большая проблема с такими макросами заключается в том, что они глобальны. Что, если две несвязанные библиотеки определяют "BUFSIZE"?
@Qwertie: Чтобы быть более понятным, макросы имеют «область действия» от #define до #undef или до конца файла. const ограничены пространствами имен, классами, функциями и блоками, как и переменные.
Мне нравится использовать «static const int x = 100;» в файлах заголовков. Тогда каждая единица перевода технически будет иметь свою собственную копию константы, но, как указывает paercebal, вы можете ожидать, что она будет встроена.
В отладчике Microsoft при наведении курсора на определенную метку const int отобразится значение, но не для меток, использующих #define. Для меня это достаточная причина, чтобы предпочесть его.
В интересах баланса в эта почта есть пример конструкции стиля C, который иногда лучше, чем эквивалент стиля C++.
Позвольте мне попробовать ... ^ _ ^ ...
Пожалуйста, дополните. Я не возражаю, но это не очевидно.
См. Мой второй ответ по этой теме.
C++ допускает только константные выражения в качестве размеров массива. По-видимому, компилятор paercebal допускает использование массивов переменной длины в качестве расширения.
Вы неправильно поняли мой пример: C++ позволяет шаблонам генерировать постоянные выражения во время компиляции. Указатель был отклонен шаблоном, что хорошо, потому что мы не хотим, чтобы код считал указатель широким массивом из 4 элементов (как макрос заставил нас поверить).
Компилятор - g ++. Это не расширение. Это полные, чистые шаблоны C++, которые работают, по крайней мере, в Visual C++ (код array_size был вдохновлен кодом strpcy_s Visual C++).
Стандарт ясен - см. 5.19. Постоянные выражения. Если у вас его нет, я уверен, что онлайн-черновик скажет то же самое. Ваш g ++ также отклонит конструкцию, если вы отключите расширения с помощью флага -pedantic.
@Fizzer: Ты прав насчет педантичной части.
@Fizzer: Тем не менее, похоже, что мой код действителен для C++ 0x ... ^ _ ^
Неважно - за тебя проголосуют. Наслаждайся своей кармой.
Обратите внимание, что это не отменяет вашу точку зрения: бывают ситуации, когда макросы являются решением. Андрей Александреску много использовал их в своей книге «Современный C++» и библиотеке C++ Loki.
C99 предоставляет VLA - массивы переменной длины. Размер массива изменяется во время выполнения в зависимости от вызова функции. Обратите внимание, что в этом случае sizeof () больше не является константой времени компиляции.
Функция C qsort по сравнению с шаблоном функции C++ sort. Последний предлагает безопасность типов с помощью шаблонов, которые имеют очевидные и менее очевидные последствия:
sort немного проще (размер элементов указывать не нужно).sort выполнит Быстрее, чем qsort, потому что вложение сравнения становится тривиальным. Это не относится к указателям на функции, которые необходимы в версии C.Следующий пример демонстрирует использование qsort по сравнению с sort на массиве int в стиле C.
int pint_less_than(void const* pa, void const* pb) {
return *static_cast<int const*>(pa) - *static_cast<int const*>(pb);
}
struct greater_than {
bool operator ()(int a, int b) {
return a > b;
}
};
template <std::size_t Size>
void print(int (&arr)[Size]) {
std::copy(arr, arr + Size, std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;
}
int main() {
std::size_t const size = 5;
int values[] = { 4, 3, 6, 8, 2 };
{ // qsort
int arr[size];
std::copy(values, values + size, arr);
std::qsort(arr, size, sizeof(int), &pint_less_than);
print(arr);
}
{ // sort
int arr[size];
std::copy(values, values + size, arr);
std::sort(arr, arr + size);
print(arr);
}
{ // sort with custom comparer
int arr[size];
std::copy(values, values + size, arr);
std::sort(arr, arr + size, greater_than());
print(arr);
}
}
Не могли бы вы добавить код в качестве примера: одно использование qsort и одно использование std :: sort?
Обратите внимание, что Бьярн Страуструп подтверждает ответ Конрада: research.att.com/~bs/bs_faq2.html#sort
Думаю, я где-то читал, что C стал лучше встраивать вызов функции через указатели на функции, но мне еще нужно найти эту статью. Во всяком случае, точка более чем верна.
Следуя сообщению fizzer на Конструкции C++, заменяющие конструкции C, я напишу здесь свой ответ:
Предупреждение: предлагаемое ниже решение C++ не является стандартным C++, а является расширением для g ++ и Visual C++ и предлагается в качестве стандарта для C++ 0x. (Спасибо за комментарии Шипучка по этому поводу)
Обратите внимание, что Йоханнес Шауб - ответ litb в любом случае предлагает другой способ, совместимый с C++ 03.
Как извлечь размер массива C?
Источник: Когда полезны макросы C++?
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])
В отличие от «предпочтительного» шаблонного решения, обсуждаемого в текущем потоке, вы можете использовать его как постоянное выражение:
char src[23];
int dest[ARRAY_SIZE(src)];
Я не согласен с Fizzer, поскольку существует шаблонное решение, способное генерировать постоянное выражение (на самом деле, очень интересная часть шаблонов - это их способность генерировать постоянные выражения при компиляции)
В любом случае, ARRAY_SIZE - это макрос, способный извлекать размер массива C. Я не буду вдаваться в подробности о макросах в C++: цель состоит в том, чтобы найти такое же или лучшее решение для C++.
Следующая версия C++ не имеет никаких проблем с макросами и может делать что-нибудь аналогичным образом:
template <typename T, size_t size>
inline size_t array_size(T (&p)[size])
{
// return sizeof(p)/sizeof(p[0]) ;
return size ; // corrected after Konrad Rudolph's comment.
}
Как демонстрирует следующий код:
#include <iostream>
// C-like macro
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])
// C++ replacement
template <typename T, size_t size>
inline size_t array_size(T (&p)[size])
{
// return sizeof(p)/sizeof(p[0]) ;
return size ; // corrected after Konrad Rudolph's comment.
}
int main(int argc, char **argv)
{
char src[23];
char * src2 = new char[23] ;
int dest[ARRAY_SIZE(src)];
int dest2[array_size(src)];
std::cout << "ARRAY_SIZE(src) : " << ARRAY_SIZE(src) << std::endl ;
std::cout << "array_size(src) : " << array_size(src) << std::endl ;
std::cout << "ARRAY_SIZE(src2) : " << ARRAY_SIZE(src2) << std::endl ;
// The next line won't compile
//std::cout << "array_size(src2) : " << array_size(src2) << std::endl ;
return 0;
}
Это выведет:
ARRAY_SIZE(src) : 23
array_size(src) : 23
ARRAY_SIZE(src2) : 4
В приведенном выше коде макрос принял указатель за массив и, таким образом, вернул неверное значение (4 вместо 23). Вместо этого шаблон отказался компилироваться:
/main.cpp|539|error: no matching function for call to ‘array_size(char*&)’|
Таким образом, демонстрируя, что шаблонное решение: * возможность генерировать постоянное выражение во время компиляции * возможность остановить компиляцию при неправильном использовании
Таким образом, аргументы в пользу шаблона следующие:
Примечание: спасибо за реализацию Microsoft strcpy_s для C++ ... Я знал, что однажды это пригодится мне ... ^ _ ^
http://msdn.microsoft.com/en-us/library/td1esda9.aspx
Fizzer правильно прокомментировал, что это недопустимо в текущем стандарте C++ и было совершенно правдой (как я мог проверить на g ++ с установленной опцией -pedantic).
Тем не менее, сегодня это можно не только использовать в двух основных компиляторах (то есть Visual C++ и g ++), но это было рассмотрено для C++ 0x, как предлагается в следующих черновиках:
Единственное изменение для C++ 0x, вероятно, выглядит примерно так:
inline template <typename T, size_t size>
constexpr size_t array_size(T (&p)[size])
{
//return sizeof(p)/sizeof(p[0]) ;
return size ; // corrected after Konrad Rudolph's comment.
}
(обратите внимание на ключевое слово constexpr)
Йоханнес Шауб - ответ litb предлагает другой способ, совместимый с C++ 03. Я скопирую сюда источник для справки, но обязательно посетите его ответ, чтобы увидеть полный пример (и модернизируйте его!):
template<typename T, size_t N> char (& array_size(T(&)[N]) )[N];
Что используется как:
int p[] = { 1, 2, 3, 4, 5, 6 };
int u[sizeof array_size(p)]; // we get the size (6) at compile time.
Многие нейроны в моем мозгу сгорели, чтобы я понял природу array_size (подсказка: это функция, возвращающая ссылку на массив из N символов).
:-)
Ваша ссылка относится к черновику "рабочего документа за ноябрь 1996 года": csci.csusb.edu/dick/c++std/cd2 ... Мои собственные ссылки относятся к 2008 году и относятся к C++ 0x
Я в курсе. Формулировка упомянутого параграфа идентична действующему стандарту.
Ты прав. Итак, я думаю, мы все согласны с тем фактом, что сегодня приведенный выше код является расширением C++, предлагаемым g ++ и Visual C++, и что C++ 0x, скорее всего, будет включать что-то подобное (то есть с использованием ключевого слова constexpr) для следующий стандарт.
Без проблем. Спасибо за столь заметное исправление.
@Fizzer: Спасибо за ваш вклад. Я многому научился сегодня благодаря обсуждению, которое мы разделили.
Э… почему бы вам просто не вернуть аргумент шаблона size в вашей функции? Расчет размера полностью избыточен.
Хорошая точка зрения. Хотя это не важно (расчет выполняется во время компиляции), я предполагаю, что Конрад прав и что параметр «размер» можно использовать напрямую. Я это проверю и исправлю код.
используя это выражение, вы можете получить полностью совместимое со стандартом постоянное выражение. не нужно ждать C++ 0x :): template <typename T, size_t N> char (& array_size (T (&) [N])) [N]; int dest2 [размер_массива (src)];
приведение C-пути (type) к static_cast<type>(). см. там и там в stackoverflow по теме
Вы должны подвести итоги, детализируя одним предложением исключительное использование каждого преобразования C++ и заканчивая чем-то вроде «В C++ мы можем ограничить область действия преобразования (const, static и т. д.), И проще найти / grep в большой проект ».
Верно, но поскольку вы были тем, кто спрашивал, и теперь я вижу, что вы знаете об этих моментах с одной стороны. И, с другой стороны, приведение C++ хорошо освещено двумя сообщениями, ссылки на которые я даю в своем ответе, я думаю, что лучше сделать это кратким. В любом случае спасибо за совет.
Объявление локальных (автоматических) переменных
(Неправда с C99, как правильно указал Джонатан Леффлер)
В C вы должны объявить все локальные переменные в начале блока, в котором они определены.
В C++ можно (и желательно) отложить определение переменной до того, как ее нужно будет использовать. Позже предпочтительнее по двум основным причинам:
Он также создает небольшие связные фрагменты кода, которые упрощают редактирование / рефакторинг.
Он также часто откладывает объявление переменной до тех пор, пока переменная не будет разумно инициализирована. Это уменьшение подверженности риску, и если переменная не относится к базовому типу, это может привести к повышению производительности.
Я считаю, что эта функция обеспечивает более чистый код. Эта функция значительно упрощает функциональную декомпозицию. Настолько, что в C я заключу свои переменные в {}, чтобы уловить их до минимально возможного количества строк. В C++ даже стоит использовать крошечный блок {}.
Это подделка - C99 (которому уже почти десять лет) предоставляет объявление переменных в стиле C++ в любой точке функции. Однако количество людей, которые им пользуются, ограничено.
AddUserName(int userid, NameInfo nameinfo);
AddUserAge(int userid, int iAge);
AddUserAddress(int userid, AddressInfo addressinfo);
User::AddInfo(NameInfo nameinfo);
User::AddInfo(int iAge);
User::AddInfo(AddressInfo addressInfo);
Позволяет программисту выразить интерфейс таким образом, чтобы концепция функции выражалась в имени, а тип параметра выражался только в самом параметре. Позволяет вызывающей стороне взаимодействовать с классом способом, более близким к выражению концепций. Также обычно приводит к более лаконичному, компактному и читаемому исходному коду.
Хотя передача по копии, вероятно, была бы лучше в качестве передачи по константной ссылке, я видел достаточно этого шаблона, чтобы поверить в то, что неиспользование перегруженной функции вместо этого действительно было «Мне неудобно с C++ "перегруженные функции". +1. (Кто, черт возьми, тебя обманул?)
@parcebal: Я, например. Эти функции следует называть по-другому. Что такое «эквивалент / замена C++» для AddUserGrade(int userid, int grade)?
Я обнаружил, что когда имеешь дело с чем угодно, кроме простейших и мельчайших случаев, идея иметь разные функции с одним и тем же именем глупа и усложняет отслеживание кода без реальной выгоды.
iostreams
Форматированный ввод-вывод может быть быстрее при использовании среды выполнения C. Но я не верю, что низкоуровневый ввод-вывод (чтение, запись и т. д.) Работает медленнее с потоками. Возможность читать или писать в поток, не заботясь о том, является ли другой конец файлом, строкой, сокетом или каким-либо определяемым пользователем объектом, невероятно полезна.
У вас есть пример кода, позволяющий младшему разработчику сравнить две версии?
В ответ на Алекс Че и справедливости ради C:
В C99, текущей спецификации стандарта ISO для C, переменные могут быть объявлены в любом месте блока, как и в C++. Следующий код действителен C99:
int main(void)
{
for(int i = 0; i < 10; i++)
...
int r = 0;
return r;
}
Я предлагаю кое-что, что, возможно, совершенно очевидно, - Пространства имен.
void PrintToScreen(const char *pBuffer);
void PrintToFile(const char *pBuffer);
void PrintToSocket(const char *pBuffer);
void PrintPrettyToScreen(const char *pBuffer);
против.
namespace Screen
{
void Print(const char *pBuffer);
}
namespace File
{
void Print(const char *pBuffer);
}
namespace Socket
{
void Print(const char *pBuffer);
}
namespace PrettyScreen
{
void Print(const char *pBuffer);
}
Это немного надуманный пример, но возможность классифицировать маркеры, которые вы определяете, в области, которые имеют смысл, предотвращает путаницу между назначением функции и контекстом, в котором она вызывается.
В c большая часть ваших динамических функций достигается за счет передачи указателей на функции. C++ позволяет иметь объекты-функции, обеспечивая большую гибкость и безопасность. Я представлю пример, адаптированный из превосходного Общие знания C++ Стивена Дьюхерста.
int fibonacci() {
static int a0 = 0, a1 =1; // problematic....
int temp = a0;
a0 = a1;
a1 = temp + a0;
return temp;
}
void Graph( (int)(*func)(void) );
void Graph2( (int)(*func1)(void), (int)(*func2)(void) );
Graph(fibonacci);
Graph2(fibonacci,fibonacci);
Вы можете видеть, что, учитывая статические переменные в функции fibonacci(), порядок выполнения Graph и Graph2() изменит поведение, несмотря на тот факт, что вызов Graph2() может иметь неожиданные результаты, поскольку каждый вызов func1 и func2 приведет к следующему значение в серии, а не следующее значение в отдельном экземпляре серии по отношению к вызываемой функции. (Очевидно, вы могли бы экстернализовать состояние функции, но в этом не было бы смысла, не говоря уже о том, что это сбивает пользователя с толку и усложняет клиентские функции)
class Fib {
public:
Fib() : a0_(1), a1_(1) {}
int operator();
private:
int a0_, a1_;
};
int Fib::operator() {
int temp = a0_;
a0_ = a1_;
a1_ = temp + a0_;
return temp;
}
template <class FuncT>
void Graph( FuncT &func );
template <class FuncT>
void Graph2( FuncT &func1, FuncT &func2);
Fib a,b,c;
Graph(a);
Graph2(b,c);
Здесь порядок выполнения функций Graph() и Graph2() не меняет результат вызова. Кроме того, при обращении к Graph2()b и c поддерживают отдельное состояние по мере их использования; каждый будет генерировать полную последовательность Фибоначчи индивидуально.
Следуя конструкции паэрцебал с использованием массивов переменной длины, чтобы обойти ограничение, заключающееся в том, что функции еще не могут возвращать константные выражения, вот способ сделать это некоторым другим способом:
template<typename T, size_t N> char (& array_size(T(&)[N]) )[N];
Я написал это в некоторых других своих ответах, но он не подходит лучше, чем в этом потоке. Итак, вот как его можно использовать:
void pass(int *q) {
int n1 = sizeof(q); // oops, size of the pointer!
int n2 = sizeof array_size(q); // error! q is not an array!
}
int main() {
int p[] = { 1, 2, 3, 4, 5, 6 };
int u[sizeof array_size(p)]; // we get the size at compile time.
pass(p);
}
Я читал и тестировал ваш код в течение 10 минут, и мне все еще интересно, что, черт возьми, такое array_size (я не очень хорошо разбираюсь в типах C++, я думаю). В любом случае, +1 за штуковину (и пока я потрачу время, пытаясь ее изучить / понять)
Ладно, я понял. Это прототип функции, возвращающей ссылку на массив из N символов. Оператор sizeof используется для получения размера возвращаемого массива символов. Я не знал, что оператор sizeof можно использовать в прототипах функций для возврата размера возвращаемого типа. Спасибо!
новое в C++ против malloc в C. (для управления памятью)
Оператор new позволяет вызывать конструкторы классов, тогда как malloc - нет.
std::copy против memcpyВо-первых, есть проблемы с удобством использования:
memcpy принимает недействительные указатели. Это отбрасывает безопасность типов.std::copy допускает перекрытие диапазонов в определенных случаях (с std::copy_backward, существующим для других случаев перекрытия), в то время как memcpy никогда не допускает этого.memcpy работает только с указателями, а std::copy работает с итераторами (из которых указатели являются особым случаем, поэтому std::copy работает и с указателями). Это означает, что вы можете, например, включить элементы std::copy в std::list.Разумеется, вся эта дополнительная безопасность и универсальность имеют свою цену, не так ли?
Когда я измерил, я обнаружил, что std::copy имел небольшое преимущество в производительности по сравнению с memcpy.
Другими словами, кажется, что нет причин использовать memcpy в реальном коде C++.
Почему бы не добавить тег C? Этот вопрос, хотя и не относится непосредственно к C, безусловно, актуален для людей, интересующихся C.