Конструкции C++, заменяющие конструкции C

После обсуждения с новым разработчиком в моей команде я понял, что в 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.

Adam Rosenfield 22.10.2008 22:58

Я согласен с Адамом насчет тега C.

Michael Burr 22.10.2008 23:03

Мои первоначальные причины заключались в том, что я не хотел трепаться на C++ и C. Это не попытка «обратить», и это не попытка похвастаться одним языком против другого. Я просто не хотел «драться» ... Тем не менее, возможно, вы правы.

paercebal 22.10.2008 23:44

Если попытки преобразования отсутствуют, почему конструкция C++ априори должна быть «такой же или даже лучше»?

fizzer 22.10.2008 23:48

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

paercebal 23.10.2008 00:02

Я добавил тег "c" после комментариев Адама и Майка. Спасибо за вклад.

paercebal 23.10.2008 00:05

Есть ли у нас «пропагандистский» ярлык? Я больше не буду участвовать в этой теме - игра сфальсифицирована.

fizzer 23.10.2008 00:11

@ fizzer.myopenid.com, я не думаю, что он говорит, что C++ лучше всегда. Думаю, он ищет примеры, когда C++ лучше. Я знаю более чем одного разработчика, которому C нравится больше, чем C++, потому что C++ может быть сложнее отлаживать из-за перегрузки оператора и подобных проблем.

Onorio Catenacci 23.10.2008 02:00

@Onorio: Я предполагаю, что произошло недоразумение: несмотря на то, что его комментарий (вероятно, написанный на скорую руку) мог поверить, Физзер внес положительный вклад в обсуждение и показал хорошее понимание C++. Мои собственные знания C++ расширились благодаря нашему обсуждению.

paercebal 23.10.2008 02:15

@Onorio: [...] Но вы четко изложили мою точку зрения, вероятно, лучше, чем я в своих сообщениях. Спасибо.

paercebal 23.10.2008 02:16

Помечено как опрос, поскольку ожидается несколько ответов, а не один.

Suma 23.10.2008 15:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
17
11
3 331
21

Ответы 21

встроенная инициализация структуры и встроенные конструкторы

Иногда нам нужно в 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
}

; Я вижу три проблемы с приведенным выше кодом:

  • если объект специально не инициализирован, он не будет инициализирован полностью
  • если мы поменяем местами x или y (по любой причине), инициализация C по умолчанию в doSomething () теперь будет неправильной
  • если мы добавим элемент z и хотим, чтобы он по умолчанию был "нулевым", нам все равно нужно будет изменить каждую встроенную инициализацию

В приведенном ниже коде конструкторы будут встроены (если они действительно полезны), и, следовательно, будет иметь нулевую стоимость (как код 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 назвал инициализированный

Адам Розенфилд сделал интересный комментарий, который я считаю очень интересным:

C99 допускает именованные инициализаторы: CRect r = {.x = 25, .y = 40}

Это не будет компилироваться на C++. Думаю, это нужно добавить в C++, хотя бы для C-совместимости. Во всяком случае, в C это облегчает проблему, упомянутую в этом ответе.

C99 допускает именованные инициализаторы: CRect r = {.x = 25, .y = 40};

Adam Rosenfield 22.10.2008 22:57

Интересный момент. Это не будет компилироваться в C++ g ++ («ошибка: ожидаемое первичное выражение перед токеном«. »»), Так что это не делает недействительным весь этот пост, но все же очень хороший момент, о котором стоит знать.

paercebal 23.10.2008 00:42

Нет, он не компилируется с g ++, но если вы компилируете код чистый C, он работает с gcc. Я тестировал его с помощью gcc 3.4.4, но уверен, что он работает и во многих других версиях gcc.

Adam Rosenfield 23.10.2008 00:53

Я не понимаю, почему встроенная инициализация «уязвима» для добавления или изменения порядка элементов, но вызов конструктора, по-видимому, не «уязвим» для добавления или изменения порядка параметров. Либо интерфейс фиксирован, либо нет, и это относится как к структуре, так и к конструктору.

Steve Jessop 23.10.2008 01:20

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

Steve Jessop 23.10.2008 01:21

@onebyone: возьмите две структуры CRect, скомпилируйте их и посмотрите результат. Затем инвертируйте объявление int x / int y внутри структуры. Вы увидите, что C CRect инвертирован, в то время как C++ CRect по-прежнему дает правильный результат. [...]

paercebal 23.10.2008 01:48

[...] «Интерфейс» C++ CRect - это конструктор (пока конструктор не изменился, все в порядке), а интерфейс C CRect - это сама структура (в момент изменения она не будет Работа). Встроенное косвенное обращение C++ с нулевой стоимостью, заданное конструктором, меняет все.

paercebal 23.10.2008 01:49

Я согласен с этим сообщением, но хочу отметить, что добавление конструктора в структуру сделает ее больше не типом POD. В C++ 0x этого не происходит.

mmocny 14.11.2008 18:13

@michalmocny: Вы правы, но «POD» не является проблемой для C++ (то есть кого это волнует?), если вы не играете с разделяемой памятью между процессами, памятью, выделенной malloc, или другими чисто-C-функциями. В этом посте я упоминал только конструкции в стиле C против конструкций C++ в чистом коде, скомпилированном на C++.

paercebal 30.11.2008 23:34

Практически любое использование void*.

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

paercebal 23.10.2008 01:54

Макросы и встроенные шаблоны

Стиль 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++:

  • Безопасность типов - обеспечивает, чтобы аргументы были одного типа.
  • Синтаксические ошибки в определении max будут указывать на правильное место, а не на то, где вы вызываете макрос.
  • Может отлаживать функцию

Кроме того, версия C++ не оценивает аргументы дважды. Рассмотрим max (++ i, ++ j): версия C будет увеличивать либо i, либо j дважды, а не один раз.

David Thornley 22.10.2008 22:49

Возможно, лучшей версией были бы две "максимальные" функции. Один ожидает константные ссылочные параметры и возвращает ссылку на константу максимума, другой ожидает неконстантных ссылочных параметров и возвращает неконстантную ссылку максимума ... Но это не тема ^ _ ^ ... + 1

paercebal 22.10.2008 22:52

Макс действительно не так прост, вам действительно стоит прочитать этот ddj.com/cpp/184403774

Motti 22.10.2008 23:00

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

JohnMcG 22.10.2008 23:15

Версия C неправильная, опасная или и то, и другое. Я добавлю скобки, необходимые для безопасности (r).

Konrad Rudolph 23.10.2008 00:03

У вас все еще неправильная версия C. Также необходимы скобки вокруг всего расширения.

Roddy 23.10.2008 00:33

Версия C может быть опасной, но я по-прежнему предпочитаю ее «80 строк исходного кода шаблона (плюс библиотека Loki), которые не будут компилироваться ни с одним доступным компилятором». (краткое изложение ссылки motti)

Roddy 23.10.2008 00:45

@Roddy: Решение JohnMcG, дополненное неконстантной версией, более чем достаточно для большинства применений. Меня еще не беспокоили std :: min и std :: max, предлагаемые STL (используйте одну строку "#include <algorithm>"), в то время как макрос "#define max" уже уничтожил идеально размещенный / инкапсулированный код.

paercebal 23.10.2008 01:59

Конечно, то, что макрос-версию сложно сделать, является частью проблемы.

JohnMcG 23.10.2008 04:12

Несмотря на то, что макросы имеют «область действия» от #define до #undef или до конца файла. template<> ограничены пространствами имен, классами, функциями и блоками, как и другие имена функций и классов.

Aaron 31.12.2008 05:37

Мне потребовалось несколько попыток, но я надеюсь, что теперь все в порядке: codepad.org/R6i0te8h. пытается работать с параметрами const / nonconst, и с их разными типами (находит их общий тип с помощью оператора? :) без локи или чего-то еще. но, может быть, есть ошибка, которую я заметил?

Johannes Schaub - litb 03.01.2009 19:58

Параметры по умолчанию:

C:

void AddUser(LPCSTR lpcstrName, int iAge, const char *lpcstrAddress);
void AddUserByNameOnly(LPCSTR lpcstrName)
  {
  AddUser(lpcstrName, -1,NULL);
  }

Замена / эквивалент C++:

void User::Add(LPCSTR lpcstrName, int iAge=-1, const char *lpcstrAddress=NULL);

Почему это улучшение:

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

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

Thomas Owens 22.10.2008 23:06

Я переформатировал сообщение, чтобы код был хорошо напечатан. Пожалуйста, Стив, не могли бы вы отредактировать свой пост, чтобы разбить его на два поста и объяснить плюсы / минусы каждого решения? (И, возможно, изменить LPCSTR на const char * для людей, не работающих с Windows?). Я жду, чтобы добавить +1 к каждому посту.

paercebal 22.10.2008 23:35

@Thomas: Я согласен, даже если это все еще зависит от контекста. Хотя этот пример можно обсудить, их сравнение с кодом C дает им значимость в текущей теме. Я удивлен, что никто не упомянул "abs" и "fabs" C против возможной функции cpp :: abs.

paercebal 22.10.2008 23:40

@paercebal. Спасибо за редактирование и предложения. Я сделал, как вы просили.

Steve 24.10.2008 20:13

Параметры по умолчанию очень полезны, но с ними нужно быть осторожным. Я видел, как ими жестоко злоупотребляли (например, «волшебные» значения по умолчанию, которые резко меняют поведение функции.)

Jonathan Grynspan 08.10.2010 01:07

RAII и вся последующая слава по сравнению с ручным сбором / выпуском ресурсов

В C:

Resource r;
r = Acquire(...);

... Code that uses r ...

Release(r);

где, например, Resource может быть указателем на память, и Acquire / Release будет выделять / освобождать эту память, или это может быть дескриптор открытого файла, где Acquire / Release откроет / закроет этот файл.

Это создает ряд проблем:

  1. Вы можете забыть вызвать Release
  2. Код не передает никакой информации о потоке данных для r. Если r приобретается и выпускается в том же объеме, код не документирует это самостоятельно.
  3. В течение времени между 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 (), поскольку это имя класса.

Aaron 31.12.2008 05:31

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

Frunsi 11.01.2010 06:47

iostream против stdio.h

В 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.

Adam Rosenfield 22.10.2008 23:38

Я согласен. Хотя это интересно (sprintf легко злоупотребить), iostreams достаточно медленные (то есть в 5 раз эквивалент stdio, если мне не изменяет память), чтобы Херб Саттер написал об этом статью. [...]

paercebal 22.10.2008 23:59

[...] Таким образом, этот ответ выходит за рамки моего требования «Мне нужно прочитать, почему конструкция C++ так же хороша или даже лучше, чем исходная конструкция C».

paercebal 23.10.2008 00:00

printf (помимо того, что он быстрее) также намного проще вводить. Все закрывающие и повторно открывающиеся кавычки (для cout) действительно замедляют вас.

paperhorse 23.10.2008 00:03

Ага. Также существует вопрос о многословности - setw, setprecision и друзья по сравнению с компактными модификаторами ширины поля и точности для% конверсий.

fizzer 23.10.2008 00:03

«Меньше для ввода» тоже кажется ложным, учитывая, что в приведенном выше примере код iostream будет иметь точно такое же количество символов, за исключением того, что «return 0» из main был исключен ...

Steve Jessop 23.10.2008 01:15

Многословие iostream сводит меня с ума, и я отказываюсь использовать его ни в чем, кроме самых тривиальных. А именно: printf ("0x% 08xn", x); vs std :: cout << std :: hex << std :: setfill ('0') << std :: setw (8) << x << std :: dec << std :: endl;

Rodyland 23.10.2008 01:17

Я согласен со всеми сообщениями о многословности iostream. Iostreams C++ безопаснее, чем любой аналог C (результат всегда будет Ok, т. Е. Без переполнения буфера и без усеченной строки), но он более подробный и медленный. Думаю, об этом следует упомянуть в ответе.

paercebal 23.10.2008 01:44

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

quinmars 23.10.2008 12:57

Должен признать, я всегда находил строки формата довольно запутанными, в первую очередь потому, что я никогда не использовал ввод-вывод в стиле C. Необходимость указывать, печатаю ли я double или int, кажется излишней и подверженной ошибкам, особенно в шаблонах. Что касается проблем с производительностью, я понимаю, что большая часть разрыва в производительности вызвана тем, что люди используют std::endl для символов новой строки, а не просто '\n', а остальное можно исправить с помощью std::cout.sync_with_stdio(false). Смотрите тайминги в ответе на stackoverflow.com/questions/1924530/…

David Stone 28.08.2012 08:58

Динамические массивы против контейнеров STL

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 лучше:

  • Их размер можно изменять, массивы имеют фиксированный размер.
  • Они безопасны в отношении исключений - если необработанное исключение возникает в части (...), тогда может произойти утечка памяти массива - контейнер создается в стеке, поэтому он будет должным образом уничтожен во время раскрутки
  • Они реализуют проверку привязки, например вектор :: at () (выход за пределы массива, скорее всего, вызовет нарушение доступа и завершит программу)
  • Их проще использовать, например vector :: clear () против ручной очистки массива
  • Они скрывают детали управления памятью, делая код более читаемым

Думаю, в 2D-массиве нет необходимости. 2D-массив для C++ может быть поврежден (т.е. одна строка может быть длиной 5 элементов, а другая - 7). В то время как <code> char a [25] [6] </code> всегда работает нормально и быстро. Тот же пример с одномерным массивом и вектором был бы лучше. Пока у нас не будет N-мерных массивов в C++

paercebal 22.10.2008 23:56

Также обратите внимание, что доступ через vector :: operator [] осуществляется так же быстро, как и доступ к массиву C через арифметику указателей или использование [].

paercebal 22.10.2008 23:56

P.S .: Когда я пишу «2D-массив для C++ может быть сломан», я пишу о выставленном векторе вектора. Частный вектор вектора в классе «Матрица» не будет жертвой этой проблемы благодаря инкапсуляции. Но, полагаю, это выходит за рамки темы.

paercebal 23.10.2008 01:41

#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 23.10.2008 00:48

@Roddy: Сегодня это нормально для константных выражений (я думаю, они будут встроены)

paercebal 23.10.2008 01:40

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

Michael Burr 23.10.2008 11:35

Одна большая проблема с такими макросами заключается в том, что они глобальны. Что, если две несвязанные библиотеки определяют "BUFSIZE"?

Qwertie 24.12.2008 22:33

@Qwertie: Чтобы быть более понятным, макросы имеют «область действия» от #define до #undef или до конца файла. const ограничены пространствами имен, классами, функциями и блоками, как и переменные.

Aaron 31.12.2008 05:36

Мне нравится использовать «static const int x = 100;» в файлах заголовков. Тогда каждая единица перевода технически будет иметь свою собственную копию константы, но, как указывает paercebal, вы можете ожидать, что она будет встроена.

Boojum 31.12.2008 06:43

В отладчике Microsoft при наведении курсора на определенную метку const int отобразится значение, но не для меток, использующих #define. Для меня это достаточная причина, чтобы предпочесть его.

Mark Ransom 08.10.2010 01:40

В интересах баланса в эта почта есть пример конструкции стиля C, который иногда лучше, чем эквивалент стиля C++.

Позвольте мне попробовать ... ^ _ ^ ...

paercebal 23.10.2008 00:16

Пожалуйста, дополните. Я не возражаю, но это не очевидно.

Konrad Rudolph 23.10.2008 00:17

См. Мой второй ответ по этой теме.

paercebal 23.10.2008 00:26

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

fizzer 23.10.2008 00:44

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

paercebal 23.10.2008 00:56

Компилятор - g ++. Это не расширение. Это полные, чистые шаблоны C++, которые работают, по крайней мере, в Visual C++ (код array_size был вдохновлен кодом strpcy_s Visual C++).

paercebal 23.10.2008 00:57

Стандарт ясен - см. 5.19. Постоянные выражения. Если у вас его нет, я уверен, что онлайн-черновик скажет то же самое. Ваш g ++ также отклонит конструкцию, если вы отключите расширения с помощью флага -pedantic.

fizzer 23.10.2008 01:06

@Fizzer: Ты прав насчет педантичной части.

paercebal 23.10.2008 01:11

@Fizzer: Тем не менее, похоже, что мой код действителен для C++ 0x ... ^ _ ^

paercebal 23.10.2008 01:21

Неважно - за тебя проголосуют. Наслаждайся своей кармой.

fizzer 23.10.2008 01:28

Обратите внимание, что это не отменяет вашу точку зрения: бывают ситуации, когда макросы являются решением. Андрей Александреску много использовал их в своей книге «Современный C++» и библиотеке C++ Loki.

paercebal 23.10.2008 01:34

C99 предоставляет VLA - массивы переменной длины. Размер массива изменяется во время выполнения в зависимости от вызова функции. Обратите внимание, что в этом случае sizeof () больше не является константой времени компиляции.

Jonathan Leffler 31.12.2008 06:54

Функция 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?

paercebal 23.10.2008 00:59

Обратите внимание, что Бьярн Страуструп подтверждает ответ Конрада: research.att.com/~bs/bs_faq2.html#sort

paercebal 23.10.2008 01:01

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

paercebal 23.10.2008 01:04

Следуя сообщению fizzer на Конструкции C++, заменяющие конструкции C, я напишу здесь свой ответ:

Предупреждение: предлагаемое ниже решение C++ не является стандартным C++, а является расширением для g ++ и Visual C++ и предлагается в качестве стандарта для C++ 0x. (Спасибо за комментарии Шипучка по этому поводу)

Обратите внимание, что Йоханнес Шауб - ответ litb в любом случае предлагает другой способ, совместимый с C++ 03.

Вопрос

Как извлечь размер массива C?

Предлагаемое решение 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++?

Следующая версия 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

Обновлено: решение представляет собой расширение, стандартизированное для C++ 0x

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)

Редактировать 2

Йоханнес Шауб - ответ 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 символов).

:-)

csci.csusb.edu/dick/c++std/cd2/expr.html. См. 5.19. Постоянные выражения: «функции ... не должны использоваться, и ... операторы вызова функции ... не должны использоваться»
fizzer 23.10.2008 01:27

Ваша ссылка относится к черновику "рабочего документа за ноябрь 1996 года": csci.csusb.edu/dick/c++std/cd2 ... Мои собственные ссылки относятся к 2008 году и относятся к C++ 0x

paercebal 23.10.2008 01:32

Я в курсе. Формулировка упомянутого параграфа идентична действующему стандарту.

fizzer 23.10.2008 01:41

Ты прав. Итак, я думаю, мы все согласны с тем фактом, что сегодня приведенный выше код является расширением C++, предлагаемым g ++ и Visual C++, и что C++ 0x, скорее всего, будет включать что-то подобное (то есть с использованием ключевого слова constexpr) для следующий стандарт.

paercebal 23.10.2008 01:52

Без проблем. Спасибо за столь заметное исправление.

fizzer 23.10.2008 01:55

@Fizzer: Спасибо за ваш вклад. Я многому научился сегодня благодаря обсуждению, которое мы разделили.

paercebal 23.10.2008 02:04

Э… почему бы вам просто не вернуть аргумент шаблона size в вашей функции? Расчет размера полностью избыточен.

Konrad Rudolph 23.10.2008 12:01

Хорошая точка зрения. Хотя это не важно (расчет выполняется во время компиляции), я предполагаю, что Конрад прав и что параметр «размер» можно использовать напрямую. Я это проверю и исправлю код.

paercebal 23.10.2008 13:11

используя это выражение, вы можете получить полностью совместимое со стандартом постоянное выражение. не нужно ждать C++ 0x :): template <typename T, size_t N> char (& array_size (T (&) [N])) [N]; int dest2 [размер_массива (src)];

Johannes Schaub - litb 04.01.2009 23:02

приведение C-пути (type) к static_cast<type>(). см. там и там в stackoverflow по теме

Вы должны подвести итоги, детализируя одним предложением исключительное использование каждого преобразования C++ и заканчивая чем-то вроде «В C++ мы можем ограничить область действия преобразования (const, static и т. д.), И проще найти / grep в большой проект ».

paercebal 23.10.2008 00:37

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

PW. 23.10.2008 15:16

Объявление локальных (автоматических) переменных

(Неправда с C99, как правильно указал Джонатан Леффлер)

В C вы должны объявить все локальные переменные в начале блока, в котором они определены.

В C++ можно (и желательно) отложить определение переменной до того, как ее нужно будет использовать. Позже предпочтительнее по двум основным причинам:

  1. Это увеличивает ясность программы (поскольку вы видите тип переменной, в которой она используется впервые).
  2. Это упрощает рефакторинг (поскольку у вас есть небольшие связанные фрагменты кода).
  3. Это повышает эффективность программы (поскольку переменные создаются именно тогда, когда они действительно необходимы).

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

Steve Fallows 23.10.2008 23:10

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

David Thornley 23.10.2008 23:18

Я считаю, что эта функция обеспечивает более чистый код. Эта функция значительно упрощает функциональную декомпозицию. Настолько, что в C я заключу свои переменные в {}, чтобы уловить их до минимально возможного количества строк. В C++ даже стоит использовать крошечный блок {}.

mat_geek 31.12.2008 05:55

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

Jonathan Leffler 31.12.2008 06:53

Перегруженные функции:

C:

AddUserName(int userid, NameInfo nameinfo);
AddUserAge(int userid, int iAge);
AddUserAddress(int userid, AddressInfo addressinfo);

Эквивалент / замена C++:

User::AddInfo(NameInfo nameinfo);
User::AddInfo(int iAge);
User::AddInfo(AddressInfo addressInfo);

Почему это улучшение:

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

Хотя передача по копии, вероятно, была бы лучше в качестве передачи по константной ссылке, я видел достаточно этого шаблона, чтобы поверить в то, что неиспользование перегруженной функции вместо этого действительно было «Мне неудобно с C++ "перегруженные функции". +1. (Кто, черт возьми, тебя обманул?)

paercebal 08.10.2010 00:44

@parcebal: Я, например. Эти функции следует называть по-другому. Что такое «эквивалент / замена C++» для AddUserGrade(int userid, int grade)?

Ben Voigt 03.07.2011 04:38

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

6502 22.05.2012 10:45

iostreams

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

У вас есть пример кода, позволяющий младшему разработчику сравнить две версии?

paercebal 25.10.2008 01:19

В ответ на Алекс Че и справедливости ради C:

В C99, текущей спецификации стандарта ISO для C, переменные могут быть объявлены в любом месте блока, как и в C++. Следующий код действителен C99:

int main(void)
{
   for(int i = 0; i < 10; i++)
      ...

   int r = 0;
   return r;
}

Я предлагаю кое-что, что, возможно, совершенно очевидно, - Пространства имен.

Переполненная глобальная область видимости c:

void PrintToScreen(const char *pBuffer);
void PrintToFile(const char *pBuffer);
void PrintToSocket(const char *pBuffer);
void PrintPrettyToScreen(const char *pBuffer);

против.

Определяемые C++ подразделения глобальной области видимости, пространства имен:

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++ Стивена Дьюхерста.

Указатели функций 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 приведет к следующему значение в серии, а не следующее значение в отдельном экземпляре серии по отношению к вызываемой функции. (Очевидно, вы могли бы экстернализовать состояние функции, но в этом не было бы смысла, не говоря уже о том, что это сбивает пользователя с толку и усложняет клиентские функции)

Объекты функций C++:

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

Преимущество перед размером

  1. Не работает для не массивов. Будет ли нет тихо работать для указателей
  2. В коде скажет, что размер массива взят.

Я читал и тестировал ваш код в течение 10 минут, и мне все еще интересно, что, черт возьми, такое array_size (я не очень хорошо разбираюсь в типах C++, я думаю). В любом случае, +1 за штуковину (и пока я потрачу время, пытаясь ее изучить / понять)

paercebal 08.10.2010 00:28

Ладно, я понял. Это прототип функции, возвращающей ссылку на массив из N символов. Оператор sizeof используется для получения размера возвращаемого массива символов. Я не знал, что оператор sizeof можно использовать в прототипах функций для возврата размера возвращаемого типа. Спасибо!

paercebal 08.10.2010 00:38

новое в 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++.

Другие вопросы по теме