Этикет C++ в отношении переменных-членов в куче

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

Ответов (4)

Решение

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

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

Некоторым классам, например std::vector строкам, карте и т. Д., Необходимо хранилище в куче для структур данных, которые они представляют. Это не считается дурным тоном; когда у вас есть автоматическое выделение vector, ожидается, что пользователь будет знать, что буфер выделяется при vector вызове конструктора:

void foo() {
    // user of vector knows a buffer that can hold at least 10 ints
    // gets allocated here.
    std::vector<int> foo(10);  
}

Точно так же, как std::string вы знаете, существует внутренний, выделенный в куче char* . Наличие одного string экземпляра обычно зависит от реализации STL; часто они подсчитываются по ссылкам.

Однако почти для всех классов STL у пользователей действительно есть выбор, куда помещать объекты, в том смысле, что они могут указать распределитель. vector определяется примерно так:

template <typename T, typename Alloc = DefaultAllocator<T> >
class vector {
    // etc.
};

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

vector<int, MyCustomAllocator> foo(10);

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

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

хм, я не совсем понимаю твой вопрос.

Если у вас есть класс:

class MyOtherClass;
class MyClass
{
  MyOtherClass* m_pStruct;
};

Тогда у клиента MyClass нет реального выбора того, как будет выделяться m_pStruct.

Но решение о том, как класс MyClass будет размещаться в стеке или куче, остается за клиентом:

MyClass* pMyClass = new MyClass;

или

MyClass myClass;

Менее важно то, куда класс помещает своих членов, чем то, что управление ими содержится внутри класса; т.е. клиентам и подклассам не нужно беспокоиться о переменной-члене объекта.

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

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

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

  • Допустим, у вашего класса очень большой буфер, например 512 КБ или 1 МБ. Если этот буфер не хранится в куче, ваши пользователи могут потенциально превысить пространство стека по умолчанию, если они создадут несколько локальных переменных вашего класса. В этом случае имеет смысл выделить буфер в вашем конструкторе и сохранить его как указатель.
  • Если вы выполняете какой-либо подсчет ссылок, вам понадобится указатель, чтобы отслеживать, сколько объектов на самом деле указывает на ваши данные.
  • Если время жизни вашей переменной-члена отличается от времени жизни вашего класса, можно использовать указатель. Прекрасным примером этого является ленивая оценка, когда вы платите только за создание участника, если пользователь об этом просит.
  • Хотя это не обязательно является прямой выгодой для ваших пользователей, время компиляции - еще одна причина использовать указатели вместо объектов. Если вы помещаете объект в свой класс, вы должны включить файл заголовка, который определяет объект, в файл заголовка для вашего класса. Если вы используете указатель, вы можете напрямую объявить класс и включить только заголовочный файл, который определяет класс, в исходные файлы, которые в нем нуждаются. В больших проектах использование форвардных объявлений может значительно ускорить время компиляции за счет уменьшения общего размера ваших единиц компиляции.

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