2013-03-22 3 views
19

Это действительно вопрос хорошей формы/лучших практик. Я использую structs в C++ для создания объектов, которые предназначены для хранения данных в основном, а не для создания класса с тонны методов доступа, которые ничего не делают, кроме получения/установки значений. Например:C++ Структуры с функциями-членами против классов с общедоступными переменными

struct Person { 
    std::string name; 
    DateObject dob; 
    (...) 
}; 

Если вы представить себе более 20 переменных там, пишу это как класс с частными лицами, членами и 40 с чем-то аксессорах боль, чтобы управлять и кажется расточительным для меня.

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

struct Person { 
    std::string name; 
    DateObject dob; 
    (...) 
    int age() {return calculated age from dob;} 
} 

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

+0

Writting методы accesor (геттер/сеттер) является хорошей идеей. Если вы просто Google немного, вы, вероятно, найдете массу обсуждений по этой теме. Совет. Большинство IDE предоставляют функциональность для генерации getter и setter автоматически. – Paranaix

+2

Типы 'class' и' struct' отличаются тем, что управление доступом по умолчанию для 'class' является' private', а управление доступом по умолчанию 'struct' по умолчанию является общедоступным. Использование ключевых слов за пределами создания типа может немного отличаться ... но вы, кажется, вкладываете много энергии в разницу между словом 'struct' и' class' в типе. Он может использоваться для документации в конкретном проекте, но это зависит от конкретного проекта. – Yakk

+2

Существует также опция бесплатной функции 'int age (const Person & person)'. Не все должно быть объектом с методами. – molbdnilo

ответ

17

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

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

    Класс имеет инвариант, когда для этого класса существует недопустимое состояние. Класс должен постоянно сохранять свой инвариант.

    Рассмотрим тип Point, который представляет собой двумерную геометрическую точку. Это должно быть просто struct с публичными данными x и y. Нет такой вещи, как недопустимая точка. Каждая комбинация значений x и y идеально подходит.

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

    См: Classes Should Enforce Invariants

  2. Non-друга функции, не являющиеся членами улучшить герметизацию.

    Нет причин, по которым ваша функция age должна быть реализована как функция-член. Результат age может быть рассчитан с использованием открытого интерфейса Person, поэтому у него нет причин быть функцией-членом. Поместите его в том же пространстве имен, что и Person, чтобы он был найден зависимым от аргумента поиска. Функции, найденные ADL, являются частью интерфейса этого класса; они просто не имеют доступа к конфиденциальным данным.

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

    См: How Non-Member Functions Improve Encapsulation

Так вот как я бы реализовать:

struct Person { 
    std::string name; 
    DateObject dob; 
}; 

int age(const Person& person) { 
    return calculated age from person.dob; 
} 
+2

Отличный ответ, и именно тот тип информации, который я искал, спасибо! – amnesia

+0

Я согласен с этим ответом. Я также добавлю, что определение «возраст» как функции-члена - это плохое контрольное соединение, вызывающие должны были бы передать текущую дату методу, чтобы иметь хорошее представление о том, что он делает, или текущая дата должна быть извлекается из другого места, что не было бы очевидно для вызывающего. «age» предоставляет больше реализации типа клиентскому интерфейсу, чем требуется. Это можно было бы разумно сохранить в качестве функции-члена, если бы эта структура никогда не становилась более сложной, но даже тогда я бы хотя бы объявила ее 'inline' и добавила параметр' currentDate'. –

+0

@Joseph Mansfield «Поместите его в том же пространстве имен, что и« Лицо », разве мы не ожидаем увидеть« пространство имен PeopleStuff {...} », инкапсулирующее ваш блок кода? Или достаточно, чтобы 'age()' находился в одном файле? – nobism

6

В C++ классы Struct являются классами, а - только различием (по крайней мере, я могу думать), что в элементах Struct являются общедоступными по умолчанию, но в классах они являются частными. Это означает, что вполне приемлемо использовать Structs так же, как и вы, - объясняет это хорошо. this article.

0

«Используйте структуру только для пассивных объектов, которые несут данные, а все остальное - это класс».

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

1

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

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

Допустим, у вас есть класс A, который имеет функцию GetSomeVariable, что является просто геттер для частной переменной:

class A 
{ 
    double _someVariable; 

public: 
    double GetSomeVariable() { return _someVariable; } 
}; 

Что делать, если двадцать лет вниз по линии, значение этой переменной изменяется, и вы должны, скажем, умножить его на 0,5? При использовании геттера это просто; просто верните переменную, умноженную на 0,5:

double GetSomeVariable() { return 0.5*_someVariable; } 

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

+0

Это хорошая идея: [избегать присвоения имени идентификатору с лидирующим подчеркиванием] (http://stackoverflow.com/a/224420/1497596). Для ваших переменных-членов класса вместо этого рассмотрите 'someVariable_' или' m_someVariable'. – DavidRR

0

Я не хочу сверкать святой войной здесь; Я обычно различаю его таким образом:

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

Если вы хотите, чтобы какой-либо держатель данных предпочитал структуру без каких-либо методов get/set.

Если это еще не все, как в данном случае «Лицо».

  1. Он моделирует реальный мир субъект,
  2. имеет определенное состояние и поведение,
  3. Взаимодействует с внешним миром,
  4. Экспонаты простые/сложные отношения с другими субъектами,
  5. может эволюционировать сверхурочно,

то это идеальный кандидат на класс.

Смежные вопросы