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

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

Примечание:

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

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

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

void Func(Base)
{

}

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

//main
Func(Derived());

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

Это стандарт C ++? и могу ли я попросить компилятор (VC ++) дать мне предупреждение при встрече с таким кодом?

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

Edit:

Большое спасибо всем за помощь!

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

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

Редактировать

Я отправил feedbak в MS и надеюсь, что они скоро это исправят:

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

-Baiyan

Ответы [ 8 ]

15 голосов
/ 24 февраля 2009

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

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

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

8 голосов
/ 24 февраля 2009

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

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:

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

4 голосов
/ 24 февраля 2009

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

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

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

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

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

1 голос
/ 24 февраля 2009

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

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

Редактировать

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

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

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

void do_something( const Concrete1& c );

Передача type 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 );

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

Так где же ошибка? Что ж, «ошибка» заключается в передаче ссылки на что-то, производное от класса, который затем обрабатывается вызываемой функцией как тип значения.

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

1 голос
/ 24 февраля 2009
class Derived: public Base{};

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

1 голос
/ 24 февраля 2009

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

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

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

1 голос
/ 24 февраля 2009

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

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

0 голосов
/ 24 февраля 2009

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

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

class Derived: public Base {};

void Func(Base)
{

}

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

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

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

...