Подтвердить что ты не робот

Сколько функций "приемлемо" для структуры С++?

Мой первый пост, поэтому, пожалуйста, пройдите ко мне!

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

Теперь, это прекрасно, но в какой момент вы начинаете думать, что что-то не просто структура больше и должно стать классом?

Вещи, которые, по моему мнению, разумны для структур:

  • только с простым кодом инициализации.
  • код сериализации, такой как операторы вставки/извлечения потока.

Вещи, о которых я не очень уверен, но, вероятно, сделаю:

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

Я не думаю, что структуры должны иметь:

  • распределение динамической памяти.
  • деструктор.
  • сложные функции-члены.

Где границы лежат?

Кроме того, разумно ли иметь экземпляры класса как члены структуры? например.

class C {private: int hiddenData; public: void DoSomething();};

struct S {int a; float b; C c; };

S s; s.c.DoSomething();

Помните, я не о том, что вы МОЖЕТЕ делать с С++, меня интересует то, что вам следует делать при разработке хорошего программного обеспечения.

Мысли?

4b9b3361

Ответ 1

Класс против структуры

Использование ключевого слова class или struct - это вопрос вкуса вместе с "чувством", которое он производит на читателя. Технически они эквивалентны, но удобство чтения лучше, если структуры используются для типов POD и C-struct для чего-либо еще.

Основные вещи, которые должны идти в конструкторе struct С++, который инициализирует данные (мне не нравится использование memset, и это может позже укусить, если POD превратится во что-то другое) или построить из других типов, но не копировать конструктор.

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

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

struct square_int {
   int operator()( int value )
   {
      return value*value;
   }
};
std::transform( v.begin(), v.end(), v.begin(), square_int() );

или

// off the top of my head
template <typename T>
struct is_pointer { enum { value = false } };

template <typename T>
struct is_pointer<T*> { enum { value = true } };

Методы участников и свободные функции

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

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

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

   // Example of symmetry with free functions where method would be asymmetric
   int main()
   {
      std::string( "Hello " ) + "world"; // compiles as free / member function
      "Hello " + std::string( "world" ); // compiles with free function, fails with member function definition of +
   }

В приведенном выше коде, если оператор + был членом метода std::string, компилятор не смог бы скомпилировать, так как он не может использовать литерал const char * в std::string для использования метода-члена.

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

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

Ответ 2

Я думаю, что есть три основные, последовательные школы мысли:

  • Люди, которые не заботятся в любом случае и используют struct и class взаимозаменяемые.
  • Люди, которые используют struct только для представления небольшого POD.
  • Люди, которые используют struct как записи.

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

(Кстати, С++ FAQ lite имеет довольно хорошее определение POD).

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

Ответ 3

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

Ответ 4

Из чтения исходного кода STL, который поставляется с Visual Studio, кажется, что один из используемых критериев - это вещи "в основном публичные" (начинаются с структуры) или "в основном частные" (начинаются с класса)?

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

Ответ 5

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

Если вам нужно быть совместимым с POD (например, с интерфейсом C), вам все равно нужно использовать struct. Однако вы можете обернуть эту структуру в класс и выставить ее с помощью функции get() для взаимодействия с C API. Или создайте вспомогательную функцию, чтобы вернуть правильную структуру POD из вашего класса.

Ответ 6

Вы можете посмотреть, что делает Стандартная библиотека. У каждой любимой структуры std:: pair есть только конструкторы.

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

Ответ 7

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

  • Конструктор по умолчанию, чтобы инициализировать члены данных до известных значений.
  • Конструктор с одним параметром для каждого элемента.
  • operator <, для легкого включения в множества и отображения.
  • оператор ==.

Ответ 8

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

class es, однако, имеют поля, которые только семантически значимы. Если по какой-то причине я или кто-то еще захочет изменить или изменить элементы данных class, это может произойти довольно свободно.

Ответ 9

Консистенция важна. Пункт конвенций должен дать общую точку отсчета для всех тех, кто читает ваш код в будущем.

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

Посмотрите на свой проект и установите стандарт или придерживайтесь уже существующего.

Хотя, я предлагаю вам попытаться избежать наследования, особенно если все, что делает структура, это POD. Ничего хуже, чем отслеживать кучу суперклассов для целого числа или char.

Ответ 10

много людей, включая меня, используют struct или class, чтобы показать намерение - Структуры для группировки "простых старых данных" и классы для инкапсулированных данных, которые имеет значимые операции.

ИМХО, эта дифференциация является непониманием, но она хорошо понимает, почему она так используется. Он основан на традиционных соглашениях и встроенных чувствах относительно класса vs struct. Я бы следил за предложением Маршалла Клайн:

7.8 Какая разница между ключевыми словами struct и class?

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

Лично я (более) использую ключевое слово struct для metafunctions

Ответ 11

Другим распространенным использованием структур является создание шаблонов и локальных классов RAII или функторов. Это разовые биты кода, которые ближе к функциям, чем к целым классам, и требуя добавления объявления public: просто глупо. Если вы посмотрите на повышение, вы увидите много чего типа:

template<
      bool C
    , typename T1
    , typename T2
    >
struct if_c
{
    typedef T1 type;
};

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

struct LogOnLeavingScope : public NonCopyable
{
    string Message;
    LogOnLeavingScope(const string &message) : Message(message) {}
    ~LogOnLeavingScope() { Log(Message); }
];

Классы-функторы предложили бы тот же аргумент:

struct Logger
{
    void operator()(const string &message) { Log(message); }
}

Я бы сказал, что нет никакой функции С++, которая подразумевает, что вы должны использовать класс вместо структуры (кроме, быть может, виртуальных функций). Все о том, какой интерфейс вы хотите представить, - используйте структуру, если вы в порядке со всем, что является публичным. Если вы обнаружите, что хотите добавить разделы private:, это хороший знак, который вам действительно нужен для класса вместо структуры.

Ответ 12

Если существуют методы доступа для членов данных, то это класс.

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

Нет причин, чтобы у структуры не было конструкторов, операторов сравнения и т.д.

Ответ 13

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

Свойства

Например, я мог бы определить структуру RECT, как показано ниже:

typedef struct tagRECT{
    int left;
    int top;
    int right;
    int bottom;

    int get_width(){return right - left;}
    int get_height(){return bottom - top;}
    int set_width(int width){right = left + width; return width;}
    int set_height(int height){bottom = top + height; return height;}
} RECT, *PRECT;

Методы "set" возвращают новое значение для поддержки цепочки назначений в том случае, если компилятор поддерживает свойства как расширение.

Простые преобразования

Для типов POD, которые хранят сложные данные, я могу включить методы, которые выполняют простые преобразования для этих данных. Очевидным примером может быть включение методов Rotate, Scale, Shear и Translate в структуре TransformationMatrix.

Операторы

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

Конструкторы

Я предпочитаю не иметь конструктор по умолчанию в своих структурах. Я не хочу выделять массив POD и терпеть вызов тысячи (или других) инициализаций по умолчанию. Если инициализация memset не будет достаточной, я предоставил метод Initialize.

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

Ответ 14

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

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

Ответ 15

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

Ответ 16

Как правило, я использую class, когда мне нужно использовать спецификаторы доступа. Это приводит к тому, что большинство моих материалов верхнего уровня остаются в качестве классов, в то время как редкие коллекции POD и мои не очень редкие внутренние классы pimpls обычно являются структурами.

Ответ 17

Мое простое правило для структур и классов:

if (data_requires_strict_alignment == TRUE) {
  use(struct);
} else {
  use(class);
}

То есть, если данные, которые вы представляете, соответствуют некоторому объекту данных, который имеет строгие требования к порядку и выравниванию членов (например, структуру данных, обмениваемую с оборудованием на уровне драйвера), используйте struct. Для всех остальных случаев используйте класс. В классах есть так много функций и возможностей, которых нет в структурах, и по моему опыту полезно использовать классы, когда это возможно, даже если вы не используете ни одну из этих дополнительных функций на данный момент (если не что иное, класс только для данных как структура с более безопасным уровнем доступа по умолчанию). Зарезервировать структуры для тех случаев, когда вам нужны уникальные свойства структуры; а именно способность указывать элементы структуры в точном порядке и с точным выравниванием/дополнением (как правило, с низкоуровневой связью, например драйверами), так что вы можете передать ее в массив байтов, использовать memcpy() на ней и т.д. Если вы следуете этой модели, тогда класс не будет использоваться как член структуры (поскольку определение класса не указывает выравнивание или предсказуемое значение для sizeof(class)). Аналогично, если вы думаете о конструкторах или операторах, используйте класс.

Это эмпирическое правило также облегчает взаимодействие с кодом C, поскольку структуры используются в соответствии с структурами C.

Ответ 18

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

Поэтому, как правило, нет смысла иметь функции struct member, потому что любая функция может изменять данные в структуре. Функция-член в структуре будет удобством, а не необходимостью.

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

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

Возвращаясь к примеру замены байтов:

struct S
{
    int data;
    S() {data = 1234;}
    void ByteSwap() {network::ByteSwap( &data );}
};

S s;
s.ByteSwap();

станет:

struct S
{
    S() {data = 1234;}
    int data;
};

namespace network
{
    void ByteSwap( int* i ) {/*byte swap *i*/}
    void ByteSwap( S* s ) {ByteSwap( &s->data );}

    S s;
    ByteSwap(&s);
}

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

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

Ответ 19

Это чисто вопрос стиля, и то, что правильно или неправильно, будет продиктовано вашим магазином. Иногда решение состоит в том, чтобы не принимать решения, но, перефразируя Rush, они все равно сделают выбор.

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