Почему нужно обеспокоиться директивами препроцессору?

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

boost::function f(boost::bind(&f, _1));

Это может внезапно оказать большое влияние для использования compressed_pair в случаях как вышеупомянутый. Что могло произойти если повышение:: свяжите хранит указатель функции и заполнителя _1 как участники сам по себе или в std::pair сам по себе? Ну, это могло чрезмерно увеличиться в размерах [до 113]. Принятие указателя функции имеет 8 байтов (весьма распространенный специально для функций членства), и заполнитель имеет один байт (см. ответ Логана для почему), затем нам, возможно, были нужны 9 байтов для связывать объекта. Из-за выравнивания это могло чрезмерно увеличить размер до 12 байтов в обычной системе на 32 бита.

boost::function поощряет его реализации применять маленькую объектную оптимизацию. Это означает, что для маленький функторы, маленький буфер, непосредственно встроенный в эти boost::function, объект используется для хранения функтора. Для больших функторов "куча" должна была бы использоваться при помощи оператора, нового для получения памяти. Вокруг повышения версия 1.34 , было решено принять эта оптимизация , потому что это было изображено, можно было получить некоторую выгоду очень высокой эффективности.

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

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

21
задан Brian Tompsett - 汤莱恩 9 June 2016 в 08:05
поделиться

13 ответов

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

#ifdef _WIN32 // _WIN32 is defined by Windows 32 compilers
#include <windows.h>
#else
#include <unistd.h>
#endif

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

#ifndef MY_CLASS_6C1147A1_BE88_46d8_A713_64973C9A2DB7_H_
#define MY_CLASS_6C1147A1_BE88_46d8_A713_64973C9A2DB7_H_
    class MyClass {
    ....
    };
#endif

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

В Makefile вы есть что-то вроде:

-D MY_APP_VERSION=4.5.1

Хотя в коде есть

cout << "My application name version: " << MY_APP_VERSION << endl;
25
ответ дан 16 October 2019 в 23:39
поделиться

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

Обратите внимание, что препроцессор C является довольно грубым механизмом для такого рода вещей; Система шаблонов C ++ предоставляет гораздо более мощную основу для построения кода во время компиляции. В других языках есть еще более мощные возможности метапрограммирования (например, макросистема Lisp).

7
ответ дан 16 October 2019 в 23:39
поделиться

Чаще всего он используется для двух вещей, которые будет труднее организовать без него:

  1. Включить охранников .
  2. Различные разделы кода для разных платформ.
5
ответ дан 16 October 2019 в 23:39
поделиться

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

#ifdef WIN32
#include <something.h>
#elseif LINUX
#include <somethingelse.h>
#endif

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

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

#define PI 3.141592654
with
const double PI=3.141592654;

Причина в том, что вы получаете правильное приведение типов и обработку типов данных.

Также

#define MAX(x,y) (x) > (y) ? (x) : (y)

Не очень хорошо, потому что вы можете писать

int i = 5
int max = MAX(i++, 6);

Препроцессор заменит это на:

int max = (i++) > (6) ? (i++) : (6);

Что явно не даст желаемого результата.

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

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

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

6
ответ дан 16 October 2019 в 23:39
поделиться

Многие языки программирования имеют средства метапрограммирования , где вы пишете код для компилятора , а не для среды выполнения.

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

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

2
ответ дан 16 October 2019 в 23:39
поделиться

Как правило, директивы препроцессора не должны использоваться. К сожалению, иногда приходится это делать в C и C ++.

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

C ++ избавлен от большинства этих проблем, но возможность все еще существует, поэтому ее все еще используют. (Интересно, что не модулярность. Мы все еще придерживаемся #include ),

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

2
ответ дан 16 October 2019 в 23:39
поделиться

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

#define IFDEBUG if(DEBUG==1)
//usage:
IFDEBUG{
  printf("Dump of stuff:.,,,");
}else{
  //..do release stuff
}

Без макросов у меня было бы (возможно, очень много) места в конечном исполняемом файле

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

1
ответ дан 16 October 2019 в 23:39
поделиться

Немного истории: C ++ был разработан на основе C, для которого препроцессор требовался гораздо больше, чем для C ++. Например, чтобы определить константу в C ++, вы должны написать что-то вроде const int foo = 4; , например, вместо #define FOO 4 , что является грубым эквивалентом C . К сожалению, слишком много людей перенесли свои привычки препроцессора с C на C ++.

Есть несколько разумных применений препроцессора в C ++. Использование #include для файлов заголовков в значительной степени необходимо. Это также полезно для условной компиляции, включая защиту заголовков, поэтому можно #include заголовок несколько раз (например, в разных заголовках) и обработать его только один раз. Оператор assert на самом деле является макросом препроцессора,

1
ответ дан 16 October 2019 в 23:39
поделиться

Это лучшая замена для получения некоторых возможностей отражения из C ++.

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

0
ответ дан 16 October 2019 в 23:39
поделиться

Но зачем вообще беспокоиться о концепции директив препроцессора? Разве невозможно написать эквивалентный код, который может присваивать значения константам, определять подпрограммы / функции / макросы и обрабатывать ошибки?

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

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

В общих источниках C ++ это часто считается плохой практикой, особенно когда есть средство сделайте это, используя другие языковые функции. Это требуется для некоторых вещей (например, программ, зависимых от платформы / сборки, и генеративных программ). Короче говоря, обычно есть замена, которая хорошо масштабируется. (например, определение константы как enum или встроенные шаблоны вместо макросов).

1
ответ дан 16 October 2019 в 23:39
поделиться

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

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

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

9
ответ дан 16 October 2019 в 23:39
поделиться

Ответил здесь .

0
ответ дан 16 October 2019 в 23:39
поделиться

Препроцессор C выполняет ряд задач, некоторые, но не все из которых имеют лучшие альтернативы в C ++. Если у C ++ есть лучшая альтернатива, используйте ее. Эти альтернативы включают шаблоны, встраивание и переменные const (оксюморон, но так их называет стандарт) вместо макросов #define.

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

Специальные расширения компилятора, управляемые через #pragma может быть неизбежным в некоторых случаях.

0
ответ дан 16 October 2019 в 23:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: