Плюсы и минусы использования вложенных классов и перечислений С++?

Каковы плюсы и минусы использования вложенных общедоступных классов и перечислений C++? Например, предположим, что у вас есть класс с именем printer, и этот класс также хранит информацию о выходных лотках, у вас может быть:

class printer
{
public:
    std::string name_;

    enum TYPE
    {
        TYPE_LOCAL,
        TYPE_NETWORK,
    };

    class output_tray
    {
        ...
    };
    ...
};

printer prn;
printer::TYPE type;
printer::output_tray tray;

Альтернативно:

class printer
{
public:
    std::string name_;
    ...
};

enum PRINTER_TYPE
{
    PRINTER_TYPE_LOCAL,
    PRINTER_TYPE_NETWORK,
};

class output_tray
{
    ...
};

printer prn;
PRINTER_TYPE type;
output_tray tray;

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

Итак, что вы предпочитаете и почему?


person Rob    schedule 19.10.2008    source источник


Ответы (13)


Вложенные классы

У классов, вложенных в классы, есть несколько побочных эффектов, которые я обычно считаю недостатками (если не чистыми антипаттернами).

Давайте представим следующий код:

class A
{
   public :
      class B { /* etc. */ } ;

   // etc.
} ;

Или даже:

class A
{
   public :
      class B ;

   // etc.
} ;

class A::B
{
   public :

   // etc.
} ;

So:

  • Привилегированный доступ: A::B имеет привилегированный доступ ко всем элементам A (методам, переменным, символам и т. д.), что ослабляет инкапсуляцию.
  • Область A является кандидатом для поиска символов: код внутри B увидит все символы из A как возможные кандидаты для поиска символов, что может запутать код.
  • forward-declaration: невозможно выполнить forward-declare A::B без полного объявления A
  • Расширяемость: невозможно добавить еще один класс A::C, если вы не являетесь владельцем класса A.
  • Многословие кода: помещение классов в классы только увеличивает размер заголовков. Вы все еще можете разделить это на несколько объявлений, но нет возможности использовать псевдонимы, импорт или использование, подобные пространству имен.

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

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

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

Вложенные перечисления

Плюсы: Все.

Кон: Ничего.

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

// collision
enum Value { empty = 7, undefined, defined } ;
enum Glass { empty = 42, half, full } ;

// empty is from Value or Glass?

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

namespace Value { enum type { empty = 7, undefined, defined } ; }
namespace Glass { enum type { empty = 42, half, full } ; }

// Value::type e = Value::empty ;
// Glass::type f = Glass::empty ;

Обратите внимание, что C++0x определил перечисление класса:

enum class Value { empty, undefined, defined } ;
enum class Glass { empty, half, full } ;

// Value e = Value::empty ;
// Glass f = Glass::empty ;

именно для таких задач.

person paercebal    schedule 19.10.2008
comment
Хорошим примером вложенного частного класса являются ссылки в списке внутри std::list. Никто не должен знать о них, использовать их или взаимодействовать с ними (или они вообще существуют ;-) - person Martin York; 19.10.2008
comment
Время от времени я использую вложенный частный класс для шаблона pImpl. - person xtofl; 20.10.2008
comment
Вложенный частный класс в шаблоне PImpl означает, что пользователь увидит класс реализации, даже если он недоступен. Простое предварительное объявление будет объявлять только имя и ничего больше. Вы сможете полностью определить реализацию в другом месте (скрытом от пользователя)... ^_^ .. - person paercebal; 31.12.2008
comment
Проблема предварительного объявления — это больше, чем просто неудобство. Вы можете застрять в цикле зависимостей, который требует распаковки проблемной области. Невложенность может привести к несогласованности дизайна, или вам придется изменить весь проект на не вложенный: / stackoverflow.com/questions/951234/ - person Catskul; 01.10.2009
comment
Я завершил раздел вложенных классов после дальнейшего изучения (подсказанное техническим обсуждением с другом). Результаты те же (если не исключение, вложенные классы НЕ в порядке), но причины более подробно описаны. - person paercebal; 06.02.2010

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

person Adam Rosenfield    schedule 19.10.2008
comment
Обратите внимание, что использование пространства имен для вложения класса позволяет пользователю предварительно объявить этот класс: т. е.: ‹code›namespace Foo { class Bar ; }. В любом случае, +1 за замечание. - person paercebal; 19.10.2008

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

Когда вы хотите использовать «внутренний» класс как самостоятельный объект, все может начать немного запутаться, и вам придется начать писать процедуры извлечения/вставки. Не красивая ситуация.

person Paul Nathan    schedule 19.10.2008

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

person carson    schedule 19.10.2008

Нет никаких плюсов и минусов как таковых использования вложенных общедоступных классов C++. Есть только факты. Эти факты предусмотрены стандартом C++. Является ли тот или иной факт о вложенных общедоступных классах C++ плюсом или минусом, зависит от конкретной проблемы, которую вы пытаетесь решить. Приведенный вами пример не позволяет судить о том, подходят ли вложенные классы или нет.

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

  1. Сделать B другом A
  2. Сделать B вложенным классом A
  3. Сделайте методы и атрибуты, которые нужны B, общедоступными членами A.

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

Еще один факт о вложенных классах заключается в том, что невозможно добавить другой класс A::C в качестве вложенного класса A, если вы не являетесь владельцем A. Однако это вполне разумно, поскольку вложенные классы имеют привилегированный доступ. Если бы можно было добавить A::C как вложенный класс A, то A::C мог бы обмануть A< /em> в предоставлении доступа к привилегированной информации; и что вы нарушаете инкапсуляцию. Это в основном то же самое, что и с объявлением friend: объявление friend не дает вам никаких особых привилегий, которые ваш друг скрывает от других; это позволяет вашим друзьям получить доступ к информации, которую вы скрываете от своих не друзей. В C++ называть кого-то другом — это альтруистический поступок, а не эгоистичный. То же самое относится к разрешению классу быть вложенным классом.

Некоторые другие факты о вложенных общедоступных классах:

  • Область A является кандидатом для поиска символов B: если вы этого не хотите, сделайте B другом A вместо вложенного класса . Однако бывают случаи, когда вам нужен именно такой поиск символов.
  • A::B не может быть предварительно объявлен: A и A::B тесно связаны. Возможность использовать A::B, не зная A, только скроет этот факт.

Подводя итог: если инструмент не соответствует вашим потребностям, не вините его; вините себя за использование инструмента; у других могут быть другие проблемы, для которых инструмент идеально подходит.

person Oswald    schedule 31.08.2013

paercebal сказал все, что я сказал бы о вложенных перечислениях.

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

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

Надеюсь это поможет. :)

person Nick    schedule 19.10.2008

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

person Eric G.    schedule 28.03.2014

Для меня большой недостаток в том, чтобы иметь его снаружи, заключается в том, что он становится частью глобального пространства имен. Если перечисление или связанный с ним класс действительно применимы только к классу, в котором они находятся, тогда это имеет смысл. Таким образом, в случае с принтером все, что включает в себя принтер, будет знать о наличии полного доступа к перечислению PRINTER_TYPE, хотя на самом деле ему не нужно знать об этом. Я не могу сказать, что когда-либо использовал внутренний класс, но для перечисления кажется более логичным держать его внутри. Как отметил другой автор, также рекомендуется использовать пространства имен для группировки похожих элементов, поскольку засорение глобального пространства имен может быть действительно плохой вещью. Раньше я работал над большими проектами, и простое создание автоматического полного списка в глобальном пространстве имен занимает 20 минут. На мой взгляд, вложенные перечисления и классы/структуры с именами, вероятно, являются самым чистым подходом.

person DavidG    schedule 19.10.2008

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

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

person Pat Notz    schedule 20.10.2008

Если вы поместите перечисление в класс или пространство имен, IntelliSense сможет дать вам рекомендации, когда вы пытаетесь запомнить имена перечислений. Конечно, мелочь, но иногда мелочи имеют значение.

person Mark Ransom    schedule 20.10.2008

Visual Studio 2008, по-видимому, не может обеспечить интеллигентность для вложенных классов, поэтому я переключился на идиому PIMPL в большинстве случаев, когда раньше у меня был вложенный класс. Я всегда помещаю перечисления либо в класс, если он используется только этим классом, либо вне класса в том же пространстве имен, что и класс, когда перечисление используется более чем одним классом.

person Brian Stewart    schedule 20.10.2008

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

Если маленький класс определен вне большого, вы можете сделать большой класс шаблоном класса и использовать любой «маленький» класс, который вам может понадобиться в будущем, с большим классом.

Общее программирование — мощный инструмент, и, ИМХО, мы должны помнить об этом при разработке расширяемых программ. Странно, что никто не упомянул этот момент.

person Yaroslav Nikitenko    schedule 11.09.2013

Единственная проблема с вложенными классами, с которой я столкнулся, заключалась в том, что С++ не позволяет нам ссылаться на объект включающего класса в функциях вложенного класса. Мы не можем сказать "Enclosing::this"

(Но, может быть, есть способ?)

person Marius Amado-Alves    schedule 31.05.2013
comment
Здесь неразбериха. Вложенный класс не подразумевает вложенный объект. Для объекта b типа A::B может не быть объекта типа A, который b является членом. Если вы хотите таких отношений, вы должны установить их сами. - person Oswald; 01.09.2013
comment
Вы правы, может и не быть. Но много раз есть. Эта идиома распространена - person Marius Amado-Alves; 11.09.2013
comment
Вы правы, может и не быть. Но много раз есть. Эта идиома распространена: class A { int m; класс B { int getM(); } Бб; } где объекты B всегда являются компонентами реального объекта A. Если C++ добавит родителя, как у нее, вы можете реализовать getM следующим образом: int A::B::getM() { return parent-›m; } (Это чисто академический подход.) - person Marius Amado-Alves; 11.09.2013