Подтвердить что ты не робот

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

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

Примечание:

1. Да, это плохой стиль программирования, и мы должны избегать таких случаев - но мы имеем дело с устаревшим кодом, и компилятор надежды может помочь идентифицировать такие случаи для нас.)

2. Я предпочитаю вариант компилятора (VС++), чтобы отключить или разрезать срез объектов, если он есть.

class Base{};
class Derived: public Base{};

void Func(Base)
{

}

//void Func(Derived)
//{
//
//}

//main
Func(Derived());

Здесь, если я прокомментирую вторую функцию, вызывается первая функция - и компилятор (как VС++, так и Gcc) чувствует себя комфортно с этим.

Является ли это стандартом С++? и могу ли я попросить компилятор (VС++) дать мне предупреждение, когда встретил такой код?

Спасибо большое!

Edit:

Спасибо всем за вашу помощь!

Я не могу найти параметр компилятора, чтобы дать сообщение об ошибке/предупреждении - я даже разместил это в форуме MSDN для консультанта компилятора VС++ без ответа. Поэтому я боюсь, что никакая функция gcc и vС++ не реализовала эту функцию.

Поэтому добавить конструктор, который будет принимать производные классы как paramter, будет лучшим решением на данный момент.

Edit

Я передаю feedbak для MS и надеюсь, что они исправит его в ближайшее время:

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=421579

-Baiyan

4b9b3361

Ответ 1

Если вы можете изменить базовый класс, вы можете сделать что-то вроде:

class Base
{
public:
// not implemented will cause a link error
    Base(const Derived &d);
    const Base &operator=(const Derived &rhs);
};

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

Ответ 2

Как вариант ответ Andrew Khosravian, я предлагаю использовать шаблонные конструкторы копий и операторы присваивания. Таким образом, вам не нужно знать все производные классы данного базового класса, чтобы защитить этот базовый класс от нарезки:

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> >.) В любом случае вы должны удалить этот код, как только вы идентифицируете все экземпляры среза объектов.

Для разработчиков-компиляторов мира: Тестирование для среза объектов - это то, что было бы полезно для создания (необязательных) предупреждений! Я не могу придумать один экземпляр, где было бы желательно поведение, и это очень часто встречается в новичком на С++-коде.

[РЕДАКТИРОВАТЬ 27/3/2015:] Как отметил Мэтт Макнаб, на самом деле вам не нужно явно объявлять конструктор копирования и оператор присваивания, как я сделал выше, поскольку они все равно будет неявно объявлен компилятором. В стандарте С++ 2003 года это явно упоминается в сноске 106 под 12.8/2:

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

Ответ 3

Я бы предложил добавить конструктор в ваш базовый класс, который явно ссылается на производный класс (с объявлением вперед). В моем простом тестовом приложении этот конструктор вызывается в разрезе. Вы могли бы хотя бы получить утверждение во время выполнения, и вы, вероятно, могли бы получить утверждение времени компиляции с умным использованием шаблонов (например: создать экземпляр шаблона таким образом, чтобы генерировать утверждение компиляции в этом конструкторе). Также могут быть способы, специфичные для компилятора, для получения предупреждений или ошибок времени компиляции при вызове явных функций; например, вы можете использовать "__declspec (устаревший)" для "конструктора среза" в Visual Studio, чтобы получить предупреждение о времени компиляции, по крайней мере, в случае вызова функции.

Итак, в вашем примере код будет выглядеть так (для Visual Studio):

class Base { ...
    __declspec(deprecated) Base( const Derived& oOther )
    {
        // Static assert here if possible...
    }
...

Это работает в моем тесте (предупреждение во время компиляции). Обратите внимание, что он не решает проблему копирования, но аналогичный построенный оператор присваивания должен делать трюк.

Надеюсь, это поможет.:)

Ответ 4

Это обычно называется Object Slicing и является достаточно известной проблемой, чтобы иметь свою собственную статью в Википедии (хотя это всего лишь короткая описание проблемы).

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

Ответ 5

На самом деле это не решение вашей непосредственной проблемы, но....

Большинство функций, которые принимают объекты класса/структуры как параметры, должны объявлять параметры типа "const X &" или "X &", если у них нет веской причины не делать этого.

Если вы всегда это делаете, срез объектов никогда не будет проблемой (ссылки не нарезаются!).

Ответ 6

class Derived: public Base{};

Вы говорите, что Derived IS Base, и поэтому он должен работать в любой функции, которая берет базу. Если это реальная проблема, возможно, наследование не то, что вы действительно хотите использовать.

Ответ 7

Лучший способ борьбы с этой проблемой - это, как правило, следовать рекомендации Скотта Мейера (см. "Эффективный С++" ) только с конкретными классами на листовых узлах вашего дерева наследования и обеспечения того, чтобы нелистные классы были абстрактными, имея хотя бы один чистый виртуальная функция (деструктор, если не что иное).

Удивительно, как часто этот подход помогает прояснить дизайн и другими способами. В любом случае усилия по изоляции общего абстрактного интерфейса обычно являются полезными проектами.

Изменить

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

Мое рассуждение таково.

Рассмотрим некоторый библиотечный код, который определяет класс Concrete1 и использует его в нижней части этой функции.

void do_something( const Concrete1& c );

Передача типа будет ссылкой для эффективности и, в целом, хорошая идея. Если библиотека считает, что Concrete1 является типом значения, реализация может решить сделать копию входного параметра.

void do_something( const Concrete1& c )
{
    // ...
    some_storage.push_back( c );
    // ...
}

Если тип объекта переданной ссылки, действительно, Concrete1, а не какой-либо другой производный тип, то этот код в порядке, обработка среза не выполняется. Общее предупреждение об этом вызове функции push_back может создавать только ложные срабатывания и, скорее всего, будет бесполезным.

Рассмотрим некоторый код клиента, который выводит Concrete2 из Concrete1 и передает его в другую функцию.

void do_something_else( const Concrete1& c );

Поскольку параметр берется с помощью ссылки, здесь нет параметра разрезания на проверяемом параметре, поэтому было бы неверно предупреждать здесь о разрезе, поскольку это может быть так, что не происходит среза. Передача в производном типе функции, которая принимает ссылку или указатель, является обычным и полезным способом использования полиморфных типов, поэтому предупреждение или отказ от этого может показаться контрпродуктивным.

Итак, где же ошибка? Ну, "ошибка" передается в ссылку на то, что происходит от класса, который затем обрабатывается так, как будто это тип значения вызываемой функцией.

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

Ответ 8

Я немного изменил ваш код:

class Base{
  public:
    Base() {}
    explicit Base(const Base &) {}
};

class Derived: public Base {};

void Func(Base)
{

}

//void Func(Derived)
//{
//
//}

//main
int main() {
  Func(Derived());
}

Явное ключевое слово будет гарантировать, что конструктор не будет использоваться в качестве оператора неявного преобразования - вы должны явно его вызывать, когда хотите его использовать.