Как сгенерировать предупреждение/ошибку компилятора при разрезании объекта
-
06-09-2019 - |
Вопрос
Я хочу знать, можно ли позволить компилятору выдавать предупреждение/ошибку для кода следующим образом:
Примечание:
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
-Байян
Решение
Если вы можете изменить базовый класс, вы можете сделать что-то вроде:
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.03.2015:] Как отметил Мэтт Макнаб, на самом деле вам не нужно явно объявлять конструктор копирования и оператор присваивания, как я сделал выше, поскольку они все равно будут неявно объявлены компилятором.В стандарте C++ 2003 года это прямо упоминается в сноске 106 под разделом 12.8/2:
Поскольку конструктор шаблона никогда не является конструктором копирования, наличие такого шаблона не подавляет неявное объявление конструктора копирования.Конструкторы шаблонов участвуют в разрешении перегрузки вместе с другими конструкторами, включая конструкторы копирования, а конструктор шаблона может использоваться для копирования объекта, если он обеспечивает лучшее соответствие, чем другие конструкторы.
Я бы предложил добавить в ваш базовый класс конструктор, который явно принимает константную ссылку на производный класс (с предварительным объявлением).В моем простом тестовом приложении этот конструктор вызывается в случае нарезки.Тогда вы могли бы, по крайней мере, получить утверждение во время выполнения, и вы, вероятно, могли бы получить утверждение во время компиляции с умным использованием шаблонов (например:создать экземпляр шаблона таким образом, чтобы в этом конструкторе генерировалось утверждение времени компиляции).Также могут существовать специфичные для компилятора способы получения предупреждений или ошибок во время компиляции при вызове явных функций;например, вы можете использовать «__declspec(deprecated)» для «конструктора среза» в Visual Studio, чтобы получить предупреждение во время компиляции, по крайней мере, в случае вызова функции.
Итак, в вашем примере код будет выглядеть так (для Visual Studio):
class Base { ...
__declspec(deprecated) Base( const Derived& oOther )
{
// Static assert here if possible...
}
...
Это работает в моем тесте (предупреждение во время компиляции).Обратите внимание, что это не решает проблему копирования, но здесь должен помочь оператор присваивания аналогичной конструкции.
Надеюсь это поможет.:)
Обычно это называют Нарезка объектов и это достаточно известная проблема, чтобы иметь собственную статью в Википедии (хотя это лишь краткое описание проблемы).
Я полагаю, что использовал компилятор, у которого было предупреждение, которое вы могли бы включить, чтобы обнаружить и предупредить об этом.Однако я не помню, какой именно.
На самом деле это не решение вашей насущной проблемы, но....
Большинство функций, которые принимают объекты класса/структуры в качестве параметров, должны объявлять параметры типа «const X&» или «X&», если только у них нет веских причин не делать этого.
Если вы всегда делаете это, нарезка объектов никогда не будет проблемой (ссылки не нарезаются!).
Обычно лучший способ борьбы с этой проблемой — следовать рекомендациям Скотта Мейера (см. Эффективный С++) наличия конкретных классов только в конечных узлах вашего дерева наследования и обеспечения абстрактности нелистовых классов за счет наличия хотя бы одной чистой виртуальной функции (если не более того, деструктора).
Удивительно, как часто этот подход помогает прояснить дизайн и другими способами.Усилия по изоляции общего абстрактного интерфейса в любом случае обычно являются стоящими усилиями по проектированию.
Редактировать
Хотя изначально я не дал этого ясно понять, мой ответ исходит из того факта, что невозможно точно предупредить о срезе объекта во время компиляции, и по этой причине это может привести к ложному чувству безопасности, если у вас есть утверждение во время компиляции. или включено предупреждение компилятора.Если вам нужно узнать о случаях нарезки объектов и исправить их, это означает, что у вас есть желание и возможность изменить устаревший код.Если это так, то я считаю, что вам следует серьезно подумать о рефакторинге иерархии классов как о способе сделать код более надежным.
Мои рассуждения таковы.
Рассмотрим некоторый библиотечный код, который определяет класс 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 Derived: public Base{};
Вы говорите, что производное — это база, и поэтому оно должно работать в любой функции, которая принимает базу.Если это реальная проблема, возможно, наследование — это не то, что вы действительно хотите использовать.
Я немного изменил ваш код:
class Base{
public:
Base() {}
explicit Base(const Base &) {}
};
class Derived: public Base {};
void Func(Base)
{
}
//void Func(Derived)
//{
//
//}
//main
int main() {
Func(Derived());
}
Ключевое слово явный гарантирует, что конструктор не будет использоваться в качестве оператора неявного преобразования — вам придется вызывать его явно, когда вы хотите его использовать.