Лучше размещать в стеке или куче в классе - PullRequest
3 голосов
/ 02 апреля 2011

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

class OutlinedText
{
protected:
    sf::Text BaseText;
    sf::Text BackgroundText;
}

sf :: Text также являются объектами. У меня вопрос, лучше ли иметь их, как указано выше, и инициализировать их следующим образом

OutlinedText::OutlinedText(std::string &text, sf::Color MainColor, sf::Color OffsetColor, sf::Font &font, sf::Vector2f Pos, unsigned int BaseFontSize, unsigned int BackFontSize, float Offset)
{
    BaseText = sf::Text(text, font, BaseFontSize);
    BaseText.SetColor(MainColor);
    BackgroundText = sf::Text(text, font, BackFontSize);
    BackgroundText.SetColor(OffsetColor);
}

или, если я получу их в качестве указателей, и выделю их новыми, как указано ниже:

BaseText = new sf::Text(text, font, BaseFontSize);
BaseText->SetColor(MainColor);

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

Заранее спасибо

Ответы [ 5 ]

12 голосов
/ 02 апреля 2011

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

Обратите внимание, что ваш вопрос несколько вводит в заблуждение: в первом случае BaseText и BackgroundText не обязательно размещаются в стеке.Если объект OutlinedText, членом которого они являются, размещен в куче или является статической переменной, то их вообще не будет в стеке.

Я думаю, что я двойникИнициализация, как у меня сейчас.

Вы: каждая переменная-член инициализируется до ввода тела конструктора, затем в теле конструктора вы назначаете переменные-члены, поэтому они эффективно «инициализируются дважды»

Вы можете (и в большинстве случаев должны) использовать список инициализации конструктора для инициализации переменных-членов:

OutlinedText::OutlinedText(std::string& text, 
                           sf::Color MainColor, 
                           sf::Color OffsetColor,
                           sf::Font& font, 
                           sf::Vector2f Pos, 
                           unsigned int BaseFontSize, 
                           unsigned int BackFontSize, 
                           float Offset)
    : BaseText(text, font, BaseFontSize),         // This is the constructor's
      BackgroundText(text, font, BackFontSize)    // initializer list
{
    BaseText.SetColor(MainColor);
    BackgroundText.SetColor(OffsetColor);
}

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

Должны ли я иметь их в качестве указателей и назначать их новыми, как показано ниже, и освобождатьих с помощью delete в деструкторе?

Нет: вам не нужно писать delete в вашей программе на C ++: вы должны использовать умный указатель (например, auto_ptr, shared_ptr илиunique_ptr, в зависимости от того, какой срок жизни объекта вам нужен), чтобы гарантировать, что динамически размещаемые объекты автоматически уничтожаются.Если вам нужно сохранить коллекцию объектов, вам следует использовать один из контейнеров стандартной библиотеки, например vector, map или set, который также автоматически очищает объекты, которые в них хранятся.

Ручное управление временем жизни динамически распределяемого объекта утомительно, и его трудно понять правильно, и его следует избегать.Вам не нужно просто «очищаться в деструкторе», вам также нужно правильно реализовать (или подавить автоматическую генерацию) конструктор копирования и оператор присваивания копии.

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

2 голосов
/ 02 апреля 2011

Когда Джеймс ответил на ваш основной вопрос, я затрону еще кое-что, что вы сказали:

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

Технически вы не инициализируете дважды, но то, что вы делаете, действительно неэффективно.Как прокомментировал @Peter Huene, вы должны использовать список инициализации:

OutlinedText::OutlinedText(
    std::string& text,
    sf::Color MainColor,
    sf::Color OffsetColor,
    sf::Font& font,
    sf::Vector2f Pos,
    unsigned int BaseFontSize,
    unsigned int BackFontSize,
    float Offset
)
  : BaseText(sf::Text(text, font, BaseFontSize)),
    BackgroundText(sf::Text(text, font, BackFontSize))
{
    BaseText.SetColor(MainColor);
    BackgroundText.SetColor(OffsetColor);
}

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

Кроме того, она нене похоже, что вы изменяете text или font, поэтому вам, вероятно, следует передавать их по const&, а не по &.(Также рассмотрите возможность передачи MainColor, OffsetColor и Pos на const&, а не на значение, если они больше sizeof(void*).)

0 голосов
/ 02 апреля 2011

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

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

  • для этого потребуется меньше общей памяти

  • менее вероятно, что он потерпит неудачу

  • копировать / перемещать проще в реализации (частоавтоматически)

  • хранилище является автоматическим (реализации интеллектуальных указателей не обязательно являются поточно-ориентированными) *

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

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

  • полиморфизм

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

  • разделяемая память (например, ее пересчет)

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

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

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

удачи!

0 голосов
/ 02 апреля 2011

И то, и другое, или лучший инженерный ответ: «Это зависит!»

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

Большие объекты или объекты, которые передаются из класса, подходят для кучи.

0 голосов
/ 02 апреля 2011

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

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

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