Недавно я наткнулся на некоторый код C ++, который выглядел так:
class SomeObject
{
private:
// NOT a pointer
BigObject foobar;
public:
BigObject * getFoobar() const
{
return &foobar;
}
};
Я спросил программиста, почему он не просто сделал foobar указателем, и он сказал, что таким образом ему не нужно беспокоиться о выделении / освобождении памяти. Я спросил, подумал ли он об использовании какого-нибудь умного указателя, он сказал, что это работает так же хорошо.
Это плохая практика? Это кажется очень хакерским.
Это плохая практика? Это кажется очень хакерским.
Это. Если класс выходит из области видимости раньше, чем указатель, переменная-член больше не будет существовать, но указатель на него все еще существует. Любая попытка разыменования этого указателя после уничтожения класса приведет к неопределенному поведению — это может привести к сбою или к трудностям поиска ошибок, когда произвольная память считывается и обрабатывается как BigObject.
если он решил использовать какой-нибудь умный указатель
Использование умных указателей, в частности std::shared_ptr<T>
или расширенная версия, будет технически работать здесь и избежать потенциального сбоя (если вы выделите через конструктор общего указателя) — однако, это также сбивает с толку, кто владеет этим указателем — класс или вызывающий объект? Кроме того, я не уверен, что вы можете просто добавить указатель на объект к умному указателю.
Оба эти двух пункта касаются технической проблемы извлечения указателя из класса, но реальный вопрос должен быть «почему?». как в «почему вы возвращаете указатель из класса?» Есть случаи, когда это единственный путь, но чаще всего вам не нужно возвращать указатель. Например, предположим, что переменная должна быть передана в C API, который принимает указатель на этот тип. В этом случае вам, вероятно, лучше будет инкапсулировать этот вызов Си в классе.
Это совершенно разумно и никак не «хакерски»; хотя может показаться, что лучше вернуть ссылку, чтобы указать, что объект определенно существует. Указатель может быть нулевым, и некоторые могут подумать, что они должны удалить его после использования.
Объект должен где-то существовать, и существовать как член объекта обычно так же хорошо, как и существовать где-либо еще. Добавление дополнительного уровня косвенности путем динамического выделения его отдельно от объекта, которому он принадлежит, делает код менее эффективным и увеличивает нагрузку на то, чтобы убедиться, что он правильно освобожден.
Конечно, функция-член не может быть const
если он возвращает неconst
ссылка или указатель на член. Это еще одно преимущество, сделав его членом: const
классификатор на SomeObject
применяется также к его членам, но не применяется к любым объектам, на которые он просто имеет указатель.
Единственная опасность состоит в том, что объект может быть уничтожен, пока у кого-то еще есть указатель или ссылка на него; но эта опасность все еще присутствует, однако вы справляетесь с этим. Здесь могут помочь умные указатели, если время жизни объекта слишком сложное, чтобы управлять им иначе.
Вы возвращаете указатель на переменную-член, а не ссылку. Это плохой дизайн.
Ваш класс управляет жизнью Foobar объекта и, возвращая указатель на его члены, вы позволяете потребителям вашего класса продолжать использовать указатель в течение времени жизни SomeObject объект. А также это позволяет пользователям изменять состояние SomeObject возражать, как они хотят.
Вместо этого вам следует реорганизовать ваш класс, чтобы включить в него методы, которые будут выполняться на панели foobar в классе SomeObject.
пс. Подумайте, как правильно называть ваши классы. Когда вы определяете это класс. Когда вы создаете экземпляр, тогда у вас есть объект этого класса.
Обычно вообще не рекомендуется возвращать указатели на внутренние данные; это препятствует тому, чтобы класс управлял доступом к его собственным данным. Но если вы хотите сделать это в любом случае, я не вижу здесь большой проблемы; это упрощает управление памятью.
Пока вызывающая сторона знает, что указатель возвращается из getFoobar()
становится недействительным, когда SomeObject
объект разрушается, это нормально. Такие оговорки и предостережения распространены в старых программах и инфраструктурах C ++.
Даже современные библиотеки должны делать это по историческим причинам. например std::string::c_str
, который возвращает указатель на внутренний буфер в строке, который становится непригодным для использования при разрушении строки.
Конечно, это трудно обеспечить в большой или сложной программе. В современном C ++ предпочтительный подход состоит в том, чтобы максимально упростить всю простую «семантику значений», чтобы время жизни каждого объекта контролировалось кодом, который использует его тривиальным способом. Так что нет голых указателей, нет явных new
или же delete
вызовы разбросаны по вашему коду и т. д., поэтому программистам не нужно вручную проверять, что они следуют правилам.
(И тогда вы можете прибегнуть к умным указателям в тех случаях, когда вы совершенно не можете избежать совместной ответственности за время жизни объекта.)
Два не связанных вопроса здесь:
1) Как бы вы хотели, чтобы ваш экземпляр SomeObject
управлять экземпляром BigObject
что это нужно? Если каждый экземпляр SomeObject
нужен свой BigObject
, затем BigObject
член данных вполне разумен. Существуют ситуации, когда вы хотите сделать что-то другое, но если такая ситуация не возникнет, придерживайтесь простого решения.
2) Хотите ли вы дать пользователям SomeObject
прямой доступ к его BigObject
? По умолчанию ответ здесь будет «нет», исходя из хорошей инкапсуляции. Но если вы хотите, то это не меняет оценку (1). Также, если вы хотите, вам не обязательно делать это с помощью указателя — это может быть ссылка или даже элемент открытых данных.
Может возникнуть третья возможная проблема, которая изменит оценку (1):
3) Хотите ли вы дать пользователям SomeObject
прямой доступ к экземпляру BigObject
что они продолжают использовать за время существования экземпляра SomeObject
что они это получили? Если так, то, конечно, член данных не годится. Правильное решение может быть shared_ptr
или для SomeObject::getFooBar
быть фабрикой, которая возвращает другой BigObject
каждый раз это называется.
В итоге:
getFooBar()
должен вернуться const BigObject*
), пока нет оснований предполагать, что этот код неверен. Могут возникнуть другие проблемы, которые делают это неправильно.const &
скорее, чем const *
, То, что вы возвращаете, не имеет никакого отношения к тому, foobar
должен быть BigObject
элемент данных.foobar
указатель или умный указатель — либо потребуется дополнительный код для создания экземпляра BigObject
указать на.