13

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

RoundPi
  • 5,819
  • 7
  • 49
  • 75
  • 2
    It is not. Closevote is wrong. – Lightness Races in Orbit Sep 22 '11 at 14:35
  • +1 why is this off topic ??!! I couldn't find the answer in c++ 03 standard(14882) or not even in stackoverflow ! – RoundPi Sep 22 '11 at 14:37
  • 1
    I'm guessing that vote went in because of the [faq#dontask]: "there is no actual problem to be solved". Or the "rant disguised as a question" thing. – Mat Sep 22 '11 at 14:38
  • 1
    rant disguised as a question... – RoundPi Sep 22 '11 at 14:43
  • Wait...why would evilDerivedOjb.message() print "Base" if it's declared virtual? – MGZero Sep 22 '11 at 14:46
  • The "No warning at all even with /Wall option" rant points at implementation problems, but "why is it allowed?" questions the standard. That indicates someone can't tell the two apart. – MSalters Sep 22 '11 at 14:46
  • I guess there are many ppl out there thinking question the standard is a waste of time, but I think by thinking through & discussion about those doubt to standard could help you better understand this language ! Thanks a lot for those contributing close this... – RoundPi Sep 22 '11 at 14:50
  • @Gob00st: Just because you can't find the answer in the standard doesn't magically make a question on-topic! – Lightness Races in Orbit Sep 22 '11 at 14:57
  • @Gob00st: You could have formulated your question differently so that it doesn't seem so much like a rant, and it would not have been closed. There are plenty of very good questions (and answers) about the C and C++ standards here. – Mat Sep 22 '11 at 14:58
  • @MGZero: Um, because `evilDerivedObj` is not a `Derived`? – Lightness Races in Orbit Sep 22 '11 at 14:58
  • 2
    @Gob00st: On the upside, it's refreshing to see someone understanding slicing. :) – Lightness Races in Orbit Sep 22 '11 at 14:58
  • @Tomalak Geret'kal I'm a bit confused. Can you explain why the behavior I'm bringing up works in the C++ example on this page? http://en.wikipedia.org/wiki/Virtual_function#C.2B.2B Is it because of the use of a pointer? – MGZero Sep 22 '11 at 15:41
  • @MGZero: Yes. It is. Look up the slicing problem on the web: there are a bazillion resources from which you can learn about it. – Lightness Races in Orbit Sep 22 '11 at 15:53
  • @ALlHaveBeenTryingToBeHelpful: Thank you all for your answers :) I knew object slicing and how it works already but I still learned a lot lot more than that ! – RoundPi Sep 22 '11 at 16:03
  • @Tomalak Geret'kal I think I understand what's going on now, actually. It seems in the example I posted, even though it's a vector of base*, it's still pointing to an object of derived, and so nothing is lost. In this case, we're just taking the derived object and putting it directly into a base object, and so we lose data in the process. – MGZero Sep 22 '11 at 16:11
  • 1
    While the wording could certainly be more diplomatic, I think it's a fair enough question. Voting to reopen. – Jerry Coffin Sep 22 '11 at 18:21

5 Answers5

14

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.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
12

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:

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.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • "is-a" relationship only means "public inheritance", then could be used for c++ polymorhism. But polymorphism only work with pointer or reference , not the object ! – RoundPi Sep 22 '11 at 14:40
  • 1
    Wouldn't it better for C++ to prevent object slicing while only allow the pointer/reference to work for is-a relationshp ??? – RoundPi Sep 22 '11 at 14:46
  • 1
    @Nawaz, I see your point. But is there any practical useage for object slicing code like "Base base = derived;" ? – RoundPi Sep 22 '11 at 15:04
  • @Gob00st: There can be. You're basically asking : is there any practical usage of `is-a` relationship? I would say, most certainly Yes. Otherwise, why would all the object oriented languages talk about this? It's inherent feature of object-orientedness. – Nawaz Sep 22 '11 at 15:06
  • @Nawaz, yes, there can be... But it seems whenever I see this, is a bad example of object slicing that's why I raised this question. – RoundPi Sep 22 '11 at 15:52
  • @Gob00st: It's because one properly doesn't know the language. But it's not that difficult. – Nawaz Sep 22 '11 at 15:54
  • @Nawaz: you say "Pointer and Reference doesn't maintain is-a relationship if the base has virtual function(s)", but they should _exactly_ maintain that relationship, unless your design violates the Liskov Substitution Principle. The 'is-a' relationship doesn't mean you can copy-assign a derived type to a base instance and slice it; it means that a derived will behave like a base _in every respect_. The confusion of 'is-a' to mean 'public inheritance' is a damaging one that hinders good OO design. Public inheritance is the language feature that supports the relationship, but doesn't imply it. – boycy Mar 15 '12 at 13:08
  • @boycy: I think there is something confusing: there is a difference between saying *"**derived object** will behave like a **base object** in every respect"* and *"**pointer** to derived object will behave like **pointer** to base object in every respect"*. – Nawaz Mar 15 '12 at 14:00
  • That is true, but I don't think it's relevant here. The `is-a` relationship is not determined by the functions being called, or whether they are called through pointer, reference or directly, but by the design intent that a derived `is-a` base, and the C++ mechanisms that support that: public inheritance and virtual function calls. – boycy Mar 19 '12 at 16:30
7

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.

Mark B
  • 95,107
  • 10
  • 109
  • 188
2

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.

Frigo
  • 1,709
  • 1
  • 14
  • 32
-1

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.

Michael J
  • 7,631
  • 2
  • 24
  • 30
  • The question has been edited. My answer was appropriate when I wrote it. The question originally asked about a security problem as the base object had access to the base data. – Michael J Sep 25 '11 at 16:10