Как сгенерировать предупреждение/ошибку компилятора при разрезании объекта

StackOverflow https://stackoverflow.com/questions/580375

Вопрос

Я хочу знать, можно ли позволить компилятору выдавать предупреждение/ошибку для кода следующим образом:

Примечание:

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());
}

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top