Как сгенерировать предупреждение / ошибку компилятора при нарезке объекта
Я хочу знать, можно ли позволить компилятору выдавать предупреждение / ошибку для кода следующим образом:
Примечание:
1. Да, это плохой стиль программирования, и нам следует избегать таких случаев - но мы имеем дело с устаревшим кодом и надеемся, что компилятор может помочь нам в выявлении таких случаев.)
2. Я предпочитаю параметр компилятора (VC++) для отключения или включения нарезки объектов, если таковой имеется.
class Base{};
class Derived: public Base{};
void Func(Base)
{
}
//void Func(Derived)
//{
//
//}
//main
Func(Derived());
Здесь, если я закомментирую вторую функцию, будет вызвана первая функция - и компилятор (как VC++, так и Gcc) чувствует себя комфортно с этим.
Это стандарт C++? и могу ли я попросить компилятор (VC++) выдавать предупреждение при встрече с таким кодом?
Огромное спасибо!!!
Редактировать:
Большое спасибо за вашу помощь!
Я не могу найти параметр компилятора, чтобы выдать ошибку / предупреждение - я даже разместил это на форуме MSDN для консультанта по компилятору VC++ без ответа. Так что я боюсь, что ни gcc, ни vc ++ не реализовали эту функцию.
Поэтому лучшим решением на данный момент будет добавление конструктора, который принимает производные классы в качестве параметра.
Редактировать
Я отправил отзыв в MS и надеюсь, что они скоро его исправят:
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=421579
-Байян
Ответов (8)8
Если вы можете изменить базовый класс, вы можете сделать что-то вроде:
class Base
{
public:
// not implemented will cause a link error
Base(const Derived &d);
const Base &operator=(const Derived &rhs);
};
В зависимости от вашего компилятора, который должен предоставить вам единицу перевода и, возможно, функцию, в которой происходит нарезка.
В качестве варианта ответа Эндрю Хосравиана я предлагаю использовать шаблонные конструкторы копирования и операторы присваивания. Таким образом, вам не нужно знать все производные классы данного базового класса, чтобы защитить этот базовый класс от нарезки:
class Base
{
private: // To force a compile error for non-friends (thanks bk1e)
// Not implemented, so will cause a link error for friends
template<typename T> Base(T const& d);
template<typename T> Base const& operator=(T const& rhs);
public:
// You now need to provide a copy ctor and assignment operator for Base
Base(Base const& d) { /* Initialise *this from d */ }
Base const& operator=(Base const& rhs) { /* Copy d to *this */ }
};
Хотя это снижает объем необходимой работы, при таком подходе вам все равно придется возиться с каждым базовым классом, чтобы защитить его. Кроме того , это может вызвать проблемы , если есть законные преобразования из Base
к SomeOtherClass
этой службе operator Base()
члена SomeOtherClass
. (В этом случае можно использовать более сложное решение boost::disable_if<is_same<T, SomeOtherClass> >
.) В любом случае вам следует удалить этот код, как только вы определили все экземпляры нарезки объектов.
Для разработчиков компиляторов во всем мире: тестирование нарезки объектов - это определенно то, для чего стоит создавать (необязательные) предупреждения! Я не могу придумать ни одного случая, когда было бы желаемое поведение, и это очень часто встречается в коде C++ новичков.
[РЕДАКТИРОВАТЬ 27/3/2015:] Как указал Мэтт Макнаб, на самом деле вам не нужно явно объявлять конструктор копирования и оператор присваивания, как я сделал выше, поскольку они все равно будут неявно объявляться компилятором. В стандарте C++ 2003 года это прямо упоминается в сноске 106 под 12.8 / 2:
Поскольку конструктор шаблона никогда не является конструктором копирования, наличие такого шаблона не подавляет неявное объявление конструктора копирования. Конструкторы шаблонов участвуют в разрешении перегрузки с другими конструкторами, включая конструкторы копирования, и конструктор шаблона может использоваться для копирования объекта, если он обеспечивает лучшее соответствие, чем другие конструкторы.
Это обычно называется нарезкой объектов и является достаточно известной проблемой, чтобы иметь отдельную статью в Википедии (хотя это лишь краткое описание проблемы).
Я полагаю, что использовал компилятор, у которого было предупреждение, которое вы можете включить, чтобы обнаружить и предупредить об этом. Однако я не помню, какой именно.
Не совсем решение вашей непосредственной проблемы, но ....
Большинство функций, которые принимают объекты класса / структуры в качестве параметров, должны объявлять параметры типа «const X &» или «X &», если у них нет очень веской причины не делать этого.
Если вы всегда будете это делать, нарезка объекта никогда не будет проблемой (ссылки не будут нарезаны!).
Я бы предложил добавить к вашему базовому классу конструктор, который явно принимает константную ссылку на производный класс (с предварительным объявлением). В моем простом тестовом приложении этот конструктор вызывается в случае нарезки. Тогда вы могли бы, по крайней мере, получить утверждение во время выполнения, и вы, вероятно, могли бы получить утверждение во время компиляции с умным использованием шаблонов (например: создать экземпляр шаблона таким образом, чтобы генерировать утверждение во время компиляции в этом конструкторе). Также могут быть специфичные для компилятора способы получения предупреждений или ошибок во время компиляции при вызове явных функций; например, вы можете использовать «__declspec (устаревший)» для «конструктора среза» в Visual Studio, чтобы получить предупреждение во время компиляции, по крайней мере, в случае вызова функции.
Итак, в вашем примере код будет выглядеть так (для Visual Studio):
class Base { ...
__declspec(deprecated) Base( const Derived& oOther )
{
// Static assert here if possible...
}
...
Это работает в моем тесте (предупреждение во время компиляции). Обратите внимание, что это не решает случай копирования, но аналогично построенный оператор присваивания должен помочь там.
Надеюсь это поможет. :)
Лучший способ справиться с этой проблемой - это обычно следовать рекомендациям Скотта Мейера (см. Эффективный C++ ) о наличии конкретных классов только в конечных узлах вашего дерева наследования и обеспечении того, чтобы нелистовые классы были абстрактными, имея по крайней мере одну чистую виртуальную функцию ( деструктор, если не что иное).
Удивительно, как часто этот подход помогает прояснить дизайн и другими способами. Попытка изолировать общий абстрактный интерфейс обычно в любом случае стоит усилий при проектировании.
Редактировать
Хотя я изначально не разъяснял это, мой ответ исходит из того факта, что невозможно точно предупредить о нарезке объекта во время компиляции, и по этой причине это может привести к ложному чувству безопасности, если у вас есть утверждение времени компиляции , или включено предупреждение компилятора. Если вам нужно узнать об экземплярах нарезки объектов и исправить их, это означает, что у вас есть желание и возможность изменить унаследованный код. Если это так, то я считаю, что вам следует серьезно подумать о рефакторинге иерархии классов как способе сделать код более надежным.
Мои рассуждения таковы.
Рассмотрим некоторый библиотечный код, который определяет класс Concrete1 и использует его в интерфейсе к этой функции.
void do_something( const Concrete1& c );
Передача типа be reference используется для повышения эффективности и в целом является хорошей идеей. Если библиотека считает Concrete1 типом значения, реализация может решить сделать копию входного параметра.
void do_something( const Concrete1& c )
{
// ...
some_storage.push_back( c );
// ...
}
Если тип объекта переданной ссылки действительно является, Concrete1
а не каким-либо другим производным типом, тогда этот код в порядке, нарезка не выполняется. Общее предупреждение при push_back
вызове этой функции может давать только ложные срабатывания и, скорее всего, будет бесполезным.
Рассмотрим некоторый код клиента , который унаследован Concrete2
от Concrete1
и передает его в другую функцию.
void do_something_else( const Concrete1& c );
Поскольку параметр берется по ссылке, здесь не происходит нарезки проверяемого параметра, поэтому было бы некорректно предупреждать о нарезке, поскольку возможно, что нарезки не происходит. Передача производного типа функции, которая принимает ссылку или указатель, - это распространенный и полезный способ использовать преимущества полиморфных типов, поэтому предупреждение или запрещение этого может показаться контрпродуктивным.
Так где же ошибка? Что ж, «ошибка» заключается в передаче ссылки на что-то, производное от класса, который затем обрабатывается вызываемой функцией как тип значения.
Как правило, не существует способа генерировать постоянно полезное предупреждение во время компиляции против нарезки объектов, и поэтому лучшая защита там, где это возможно, - это устранение проблемы путем разработки.
Я немного изменил ваш код:
class Base{
public:
Base() {}
explicit Base(const Base &) {}
};
class Derived: public Base {};
void Func(Base)
{
}
//void Func(Derived)
//{
//
//}
//main
int main() {
Func(Derived());
}
Ключевое слово explicit гарантирует, что конструктор не будет использоваться как оператор неявного преобразования - вы должны вызывать его явно, когда хотите его использовать.