Какая функциональность является «приемлемой» для структуры C ++? - PullRequest
16 голосов
/ 17 марта 2009

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

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

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

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

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

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

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

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

  1. динамическое выделение памяти.
  2. деструктор.
  3. сложные функции-члены.

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

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

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

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

S s; s.c.DoSomething();

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

Мысли

Ответы [ 19 ]

0 голосов
/ 25 марта 2009

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

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

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

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

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

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

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

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

0 голосов
/ 18 марта 2009

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

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

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

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

0 голосов
/ 18 марта 2009

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

0 голосов
/ 18 марта 2009

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

0 голосов
/ 17 марта 2009

Я всегда использую структуры для «кусков данных», даже если они декорируются конструктором, а иногда и операторами сравнения, они никогда не добавляют к ним методы (даже методы get / set).

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

0 голосов
/ 17 марта 2009

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

Свойства

Например, я мог бы определить структуру 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.

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

0 голосов
/ 17 марта 2009

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

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

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

0 голосов
/ 17 марта 2009

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

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

0 голосов
/ 17 марта 2009

Другое распространенное использование структур - для материала шаблона и локальных классов 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); }
}

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

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