Почему в С++ требуется "срез объекта"? Почему это разрешено? Для большего количества ошибок?

Почему стандарт С++ разрешает срез объекта?

Пожалуйста, не объясняйте мне концепт среза объекта С++, поскольку я знал это.

Мне просто интересно, какое намерение стоит за этой конструкцией С++ (объект slice)?

Чтобы получить новичков больше ошибок?

Не было бы более безопасным для С++ предотвращать срез объекта?

Ниже приведен только стандартный и стандартный пример среза:

class Base{
public:
       virtual void message()
       {
               MSG("Base ");
       }
private:
    int m_base;
};

class Derived : public Base{
public:
       void message()
       {
               MSG("Derived "); 
       }
private:
       int m_derive;
};

int main (void)
{
    Derived dObj;

    //dObj get the WELL KNOWN c++ slicing below
    //evilDerivedOjb is just a Base object that cannot access m_derive
    Base evilDerivedOjb = dObj;  //evilDerivedObj is type Base
    evilDerivedOjb.message();    //print "Baes" here of course just as c++ standard says
}

Спасибо заранее.

=============================================== ================================== Прочитав все ответы и комментарии, я думаю, что должен лучше выразить свой вопрос в первую очередь, но здесь он приходит:

Если существует отношение is-a (public inheritnace), вместо частного/защищенного наследования, вы можете сделать следующее:

class Base{
public:
    virtual void foo(){MSG("Base::foo");}
};

class Derived : public Base{
public:
    virtual void foo(){MSG("Derived::foo");}
};

int main (void)
{
    Base b;
    Derived d;
    b = d;                      //1
    Base * pB = new Derived();  //2
    Base& rB = d;               //3

    b.foo();    //Base::foo
    pB->foo();  //Derived::foo
    rB.foo();   //Derived::foo
}

Хорошо известно, что только 2 и 3 работают полиморфно, в то время как один является позорным объектом которые не производят ничего, кроме ошибки!

Примечание 1, 2 и 3 НУЖД - это отношение к работе.
Если вы используете приватное/защищенное наследование, вы получите ошибку компиляции для всех из них:

'type cast' : conversion from 'Derived *' to 'const Base &' exists, but is inaccessible
'type cast' : conversion from 'Derived *' to 'Base *' exists, but is inaccessible
'type cast' : conversion from 'Derived *' to 'Base &' exists, but is inaccessible

Итак, мой вопрос (первоначальное намерение) состоял в том, чтобы спросить, было бы лучше, если бы стандарт С++ сделать 1 ошибку компиляции, сохраняя возможность 2 и 3?

Надеюсь, что на этот раз я выразил свой вопрос.

Спасибо

Ответ 1

Это разрешено из-за отношения is-a.

Когда вы публично 1 выводите Derived из Base, вы комментируете компилятор, что Derived - Base. Следовательно, это должно быть разрешено:

Base base = derived;

а затем используйте Base как есть. то есть:

base.message(); //calls Base::message();

Прочтите это:

1. Если вы в частном порядке получаете Derived из Base, то это отношение has-a. Это своего рода композиция. Прочитайте this и this.

Однако в вашем случае, если вы не хотите нарезки, вы можете сделать это:

Base & base = derived;
base.message(); //calls Derived::message();

Из вашего комментария:

Не лучше ли для С++ предотвращать нарезку объектов, а только разрешить работу указателя/ссылки - is relationshp???

Нет. Указатель и ссылка не поддерживают отношения is-a, если база имеет виртуальные функции.

 Base *pBase = &derived;
 pBase->message(); //doesn't call Base::message(). 
 //that indicates, pBase is not a pointer to object of Base type.

Если вы хотите, чтобы один объект одного типа вел себя как объект его базового типа, то это называется отношением is-a. Если вы используете указатель или ссылку базового типа, то он не будет вызывать Base::message(), который указывает, указатель или ссылка не имеет указателя или ссылки на объект базового типа.

Ответ 2

Я думаю, вы смотрите на него назад.

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

То, что объекты могут быть скопированы "статически", является фундаментальной особенностью С++ и C, и вы не сможете ничего сделать иначе.

Редактировать: [Джерри Коффин (надеюсь, что Томалак простит мой захват его ответа)]. Большая часть того, что я добавляю, идет по тем же линиям, но немного больше из источника. Единственное исключение (как вы увидите) заключается в том, что, как ни странно, кто-то действительно сказал "нам нужно нарезать на этом языке". Бьярне немного рассказывает о разрезе в дизайне и эволюции С++ (§11.4.4). Среди прочего он говорит:

Я считаю, что нарезка с практической точки зрения, но я не вижу никакого способа предотвратить ее, кроме как добавив очень специальное правило. Кроме того, в то время у меня был независимый запрос именно этой "срезающей семантики" от Рави Сети, который хотел это с теоретической и педагогической точек зрения: если вы не можете присвоить объект производного класса объекту своей общественной базы class, то это будет единственной точкой в ​​С++, где производный объект не может использоваться вместо базового объекта.

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

Ответ 3

Как вы могли бы предотвратить нарезку объектов внутри языка? Если функция ожидает 16 байт в стеке (например, как параметр), и вы передаете больший объект, который говорит 24 байта, как на Земле узнает, что делать? С++ не похож на Java, где все является ссылкой под капотом. Короткий ответ заключается в том, что просто невозможно избежать обрезки объектов, предполагая, что С++, например C, позволяет использовать семантику значений и ссылок для объектов.

РЕДАКТИРОВАТЬ: Иногда вам все равно, если предметные фрагменты и запрет на него могут помешать полезной функции.

Ответ 4

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

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

Одно решение состоит в проверке typeid как источника, так и целевых объектов при назначении, бросая исключение, если они не совпадают. К сожалению, это не применимо к конструкторам копирования, вы не можете указать тип объекта, который будет сконструирован, он будет сообщать Base даже для Derived.

Еще одно решение - запретить копирование и назначение, наследуя конфиденциально от boost::noncopyable или делая частный конструктор копирования и оператор присваивания закрытым. Первый запрещает компилятор, созданный конструктором копии и оператором присваивания, также работать во всех подклассах, но вы можете определить пользовательские в подклассах.

Еще одно решение - сделать конструктор копирования и оператор присваивания protected. Таким образом, вы все равно можете использовать их для облегчения копирования подклассов, но посторонний не может случайно обрезать объект таким образом.

Лично я получаю конфиденциально от своего собственного NonCopyable, который почти такой же, как и Boost. Кроме того, при объявлении типов значений я публично и фактически выводю их из Final<T> (и ValueType), чтобы предотвратить какой-либо полиморфизм. Только в режиме DEBUG, поскольку они увеличивают размер объектов, а статическая структура программы не изменяется в любом случае в режиме деблокирования.

И я должен повторить: срез объектов может происходить где угодно, где вы читаете переменные Base и делаете что-то с ними, убедитесь, что ваш код не распространяет его или ведет себя некорректно, когда это происходит.

Ответ 5

Каким образом базовый объект имеет доступ к m_base?

Вы не можете сделать baseObj.m_base = x; Это частный член. Вы можете использовать только общедоступные методы из базового класса, поэтому он не сильно отличается от создания базового объекта.