Как заказать функции в C ++? - PullRequest
5 голосов
/ 28 ноября 2010

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

void bar()
{
}

void foo()
{
    bar();
}

Однако в C ++ существует несколько типов функций:

  • Свободные функции
  • Закрытые функции-члены
  • Открытые функции-члены
  • Статические функции-члены

В настоящее время порядок моих функций зависит от порядка их расположения в файле .hpp, например:

class Foo_bar {
public:
    Foo_bar();
    void foo();
private:
    int some_member;
    void bar();

Но теперь, если конструктор использует foo () или bar (), это будет ниже конструктора в исходном файле, что не соответствует моему обычному порядку.Конечно, я мог бы переупорядочить свой заголовок, чтобы учесть это:

class Foo_bar {
private:
    int some_member;
    void bar();

public:
    void foo();
    Foo_bar();

Но я думаю, что это беспорядок.

Более того, в Java противоположность моему первому примеру кажется распространенной:

void foo()
{
    bar();
}

void bar()
{
}

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

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

Ответы [ 6 ]

6 голосов
/ 28 ноября 2010

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

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

Итак, вы должны быть в состоянии сделать это в C ++:

void bar();  // forward declaration; note that function bar isn't defined yet

void foo()
{
    bar();   // foo knows that bar is declared, so it will search for bar's definition
}

void bar()   // here bar is defined, so foo will use this when needed
{
}
2 голосов
/ 28 ноября 2010

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

Есть 3 вида людей, которые будут читать код класса:

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

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

class Foo
{
public:
  // types
  // static methods
  // methods (usually constructors,
  //          then simple accessors,
  //          then more complicated stuff)

protected:
  // same pattern

private:
  // same pattern
  // attributes
};

// Free functions about this class

// Implementation of inline / template methods

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

Тогда, что касается "вспомогательных" методов, это зависит от типа кода:

  • для кода шаблона, я использую пространство имен «details», читателю ясно, что он не должен беспокоиться об этом, и он изолирует имена в их собственном пространстве имен, чтобы они не появлялись в инструментах завершения кода
  • для обычного кода я использую анонимное пространство имен в исходном файле, что даже лучше, так как тогда он фактически генерирует невидимые символы, и я не рискую нарушить ODR .

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

include/
  foo.hpp

src/
  fooImpl.hpp --> #include "foo.hpp"
  foo.cpp     --> #include "fooImpl.hpp"

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

И, конечно, всегда, чтобы было проще, я всегда одинаково упорядочиваю список объявлений и список определений ...

1 голос
/ 28 ноября 2010

Ваша обеспокоенность по поводу переупорядочения функций в определении класса не является правильной, что объясняется следующими двумя цитатами из стандарта C ++ 03.

$ 9.2 / 2- «Класс считаетсяполностью определенный тип объекта (3.9) (или завершенный тип) при закрытии} спецификатора класса. В спецификации члена класса класс рассматривается как завершенный в телах функций, аргументах по умолчанию и конструкторе ctor-initializer(включая такие вещи во вложенных классах). В противном случае он рассматривается как неполный в своей собственной спецификации членов класса. "

И

$ 3.4.1 /8 - «Имя, используемое в определении функции-члена (9.3) класса X после объявления функции ID29), должно быть объявлено одним из следующих способов:

- до его использования в блоке, в котором оноиспользуется или во включающем блоке (6.3), или

- должен быть членом класса X или членом базового класса X (10.2), или

- если X является вложенным классом класса Y (9.7), должен быть членом Y или должен быть членом базового класса Y (этот поиск применяется, в свою очередь, к включающим классы Yначиная с самого внутреннего включающего класса), 30) или

- если X является локальным классом (9.8) или является вложенным классом локального класса, перед определением класса X в блоке, включающем определениекласса X, или

- если X является членом пространства имен N, или является вложенным классом класса, который является членом N, или является локальным классом или вложенным классом в локальном классефункция, которая является членом N, перед определением функции-члена, в пространстве имен N или в одном из вложенных пространств имен N.

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

Следовательно, это означает, что функции-члены класса, которые должны вызываться конструктором, не нужно определять перед конструктором лексически.

1 голос
/ 28 ноября 2010

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

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

Я обычно заказываю функции в "функции", и группирую, например. геттеры и сеттеры, конструктор / деструктор (если возможно).

1 голос
/ 28 ноября 2010

Вы объявляете класс в заголовочном файле, верно?И реализовать большую часть этого в отдельном файле?Если вы просто реализуете конструктор в файле реализации, а не в заголовке, я не думаю, что вы столкнетесь с упомянутой вами проблемой (поскольку весь заголовок будет виден до того, как конструктор вызовет foo() или bar().

0 голосов
/ 28 ноября 2010

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

Хотя есть несоответствия. Например, в классе это означает, что сначала нужно размещать общедоступные вещи, а позже - внутренние. Но видимость по умолчанию для класса (то, что вы, естественно, получаете) является закрытой, и (особенно если у меня есть встроенные методы), я обычно помещаю любые личные данные впереди. Это может быть даже ошибкой для метода встроенного стиля ссылаться на переменную-член до того, как она будет определена - извините, у меня временная проблема с памятью.

Но, по сути, главное собрать вещи, которые похожи или логически связаны. Метод begin будет смежным с методом end, метод Step_To_Next, смежный с методом Step_To_Prev, и т. Д. Группировка схожих целей, одинаковые параметры и общее использование вместе - все хорошо.

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

Наиболее важно (1) придерживаться единого стиля и (2) не слишком беспокоиться о неоднозначных случаях.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...