Why is 'object slice' needed in C++ ? Why it is allowed ? For more bugs?

泄露秘密 提交于 2019-11-27 06:02:51

问题


Why C++ standard allow object slice ?

Please don't explain c++ object slice concept to me as I knew that.

I am just wondering what's the intention behind this c++ feature(object slice) design ?

To get novice more bugs?

Wouldn't it be more type safe for c++ to prevent object slice ?

Below is just a standard and basic slice example:

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
}

Thanks in advance.

================================================================================= After reading all the answers and comments I think I should express my question better in the first place but here it comes:

When there is a is-a relationship(public inheritnace), instead of private/protected inheritance , you can do the following:

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
}

It's well known that only 2 & 3 works polymorphically while one is the infamous object slicing which produce nothing but a bug !

Note 1, 2 and 3 NEED is-a relationship to work.
If you are using private/protect inheritance, you will get compile error for all of them :

'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

So my question(original intention) was to ask would it be better if c++ standard make 1 a compile error while keep allowing 2 and 3 ?

Hope I have expressed my question better this time.

Thanks


回答1:


It's allowed because of is-a relationship.

When you publicly1 derive Derived from Base, you're annoucing to the compiler that Derived is a Base. Hence it should be allowed to do this:

Base base = derived;

and then use base as it is. that is:

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

Read this:

  • Is-A Relationship

1. If you privately derive Derived from Base, then it is has-a relationship. That is sort of composition. Read this and this.

However, in your case, if you don't want slicing, then you can do this:

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

From your comment :

Wouldn't it better for C++ to prevent object slicing while only allow the pointer/reference to work for is-a relationshp ???

No. Pointer and Reference doesn't maintain is-a relationship if the base has virtual function(s).

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

When you want one object of one type to behave like an object of it's base type, then that is called is-a relationship. If you use pointer or reference of base type, then it will not call the Base::message(), which indicates, pointer or reference doesn't have like a pointer or reference to an object of base type.




回答2:


I think you're looking at it backwards.

Nobody sat down and said "OK, we need slicing in this language." Slicing in itself isn't a language feature; it's the name of what happens when you meant to use objects polymorphically but instead went wrong and copied them. You might say that it's the name of a programmer bug.

That objects can be copied "statically" is a fundamental feature of C++ and C, and you wouldn't be able to do much otherwise.

Edit: [by Jerry Coffin (hopefully Tomalak will forgive my hijacking his answer a bit)]. Most of what I'm adding is along the same lines, but a bit more directly from the source. The one exception (as you'll see) is that, strangely enough, somebody did actually say "we need slicing in this language." Bjarne talks a bit about slicing in The Design and Evolution of C++ (§11.4.4). Among other things he says:

I'm leery of slicing from a practical point of view, but I don't see any way of preventing it except by adding a very special rule. Also, at the time, I had an independent request for exactly these "slicing semantics" from Ravi Sethi who wanted it from a theoretical and pedagogical point of view: Unless you can assign an object of a derived class to an object of its public base class, then that would be the only point in C++ where a derived object can't be used in place of a base object.

I'd note that Ravi Sethi is one of the authors of the dragon book (among many other things), so regardless of whether you agree with him, I think it's easy to understand where his opinion about language design would carry a fair amount of weight.




回答3:


How would you prevent object slicing within the language? If a function is expecting 16 bytes on the stack (as a parameter for example) and you pass a bigger object that's say 24 bytes how on Earth would the callee know what to do? C++ isn't like Java where everything is a reference under the hood. The short answer is that there's just no way to avoid object slicing assuming that C++, like C, allows value and reference semantics for objects.

EDIT: Sometimes you don't care if the object slices and prohibiting it outright would possibly prevent a useful feature.




回答4:


Object slicing is a natural consequence of inheritance and substitutability, it is not limited to C++, and it was not introduced deliberately. Methods accepting a Base only see the variables present in Base. So do copy constructors and assignment operators. However they propagate the problem by making copies of this sliced object that may or may not be valid.

This most often arises when you treat polymorphic objects as value types, involving the copy constructor or the assignment operator in the process, which are often compiler generated. Always use references or pointers (or pointer wrappers) when you work with polymorphic objects, never mix value semantics in the game. If you want copies of polymorphic objects, use a dynamic clone method instead.

One half-solution is to check the typeid of both the source and the destination objects when assigning, throwing an exception if they do not match. Unfortunately this is not applicable to copy constructors, you can not tell the type of the object being constructed, it will report Base even for Derived.

Another solution is to disallow copying and assigning, by inheriting privately from boost::noncopyable or making the copy constructor and assignment operator private. The former disallows the compiler generated copy constructor and assignment operator from working in all subclasses as well, but you can define custom ones in subclasses.

Yet another solution is to make the copy constructor and assignment operator protected. This way you can still use them to ease the copying of subclasses, but an outsider can not accidentally slice an object this way.

Personally I derive privately from my own NonCopyable, which is almost the same as the Boost one. Also, when declaring value types, I publicly and virtually derive them from Final<T> (and ValueType) to prevent any kind of polymorphism. Only in DEBUG mode though, since they increase the size of objects, and the static structure of the program doesn't change anyway in release mode.

And I must repeat: object slicing can occur anywhere where you read the variables of Base and do something with them, be sure your code does not propagate it or behave incorrectly when this occurs.




回答5:


Exactly what access does the base object have to m_base?

You can't do baseObj.m_base = x; It is a private member. You can only use public methods from the base class, so it is not much different to just creating a base object.



来源:https://stackoverflow.com/questions/7516545/why-is-object-slice-needed-in-c-why-it-is-allowed-for-more-bugs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!