What is object slicing?
Asked Answered
T

17

915

In C++, what is object slicing and when does it occur?

Thorlie answered 8/11, 2008 at 11:10 Comment(0)
P
731

"Slicing" is where you assign an object of a derived class to an instance of a base class, thereby losing part of the information - some of it is "sliced" away.

For example,

class A {
   int foo;
};

class B : public A {
   int bar;
};

So an object of type B has two data members, foo and bar.

Then if you were to write this:

B b;

A a = b;

Then the information in b about member bar is lost in a.

Postmark answered 8/11, 2008 at 11:22 Comment(16)
Very informative, but see stackoverflow.com/questions/274626#274636 for an example of how slicing occurs during method calls (which underscores the danger a little better than the plain assignment example).Clear
Interesting. I've been programming in C++ for 15 years and this issue never occurred to me, as I've always passed objects by reference as a matter of efficiency and personal style. Goes to show how good habits can help you.Radiotelegraph
@David : can you explain your line in detail Then the information in b about member bar is lost in a. . does foo data gets corrupted after assignment? but why?Mechanistic
@Hades: no data gets corrupted. It is just impossible to say a.bar because the compiler thinks that a is of type A. If you cast it back to type B then you can retrieve all hidden ("sliced away") fields.Palmette
@Felix Thanks but I don't think casting back (since not a pointer arithmetic) will work , A a = b; a is now object of type A which has copy of B::foo. It will be mistake to cast it back now i think.Mechanistic
@Hades: I see what you mean, I was considering pointers. You're right, assignment is not casting of course – in fact, a new object is allocated on the stack. Then bar in b is not corrupted at all, simply not copied by the compiler-generated assignment operator, so a is now a completely new object of type A with member a.foo set to the same value as b.foo.Palmette
@Karl: it does not help in the case of assignment. B& b = xxx; b = someDerivedClass(); still provoke slicing. It is just that usually the problem goes unnoticed.Sfumato
Another answer that explains slicing but not the problem.Lot
This isn't "slicing", or at least a benign variant of it. The real problem occurs if you do B b1; B b2; A& b2_ref = b2; b2 = b1. You might think you have copied b1 to b2, but you haven't! You have copied a part of b1 to b2 (the part of b1 that B inherited from A), and left the other parts of b2 unchanged. b2 is now a frankensteinian creature consisting of a few bits of b1 followed by some chunks of b2. Ugh! Downvoting because I think the answer is very missleading.Hatter
@Hatter Your comment should read B b1; B b2; A& b2_ref = b2; b2_ref = b1 "The real problem occurs if you" ... derive from a class with a non-virtual assignment operator. Is A even intended for derivation? It has no virtual functions. If you derive from a type, you have to deal with the fact that its member functions can be called!Mullinax
A link with a couple of examples of how to avoid the slicing problem by using pointers or referencesAdrian
@Hatter that's why you don't put B into a name of reference to A.Tbar
This answer is wrong. No information is lost here. a is capable of holding just one field and gets a copy of this field from b. It is never supposed to deal with whatever other fields b may have.Isis
What is the signature of the implicitly-generated copy constructor used in the declaration of A? i.e. in A a = b;?Clothesbasket
It's A(const A&).Clothesbasket
@KarlBielefeldt Pass by value isn't a matter of preference or only efficiency. If your function needs a value it can change freely without affecting the caller, you'll have to use pass by value.Stamata
H
625

Most answers here fail to explain what the actual problem with slicing is. They only explain the benign cases of slicing, not the treacherous ones. Assume, like the other answers, that you're dealing with two classes A and B, where B derives (publicly) from A.

In this situation, C++ lets you pass an instance of B to A's assignment operator (and also to the copy constructor). This works because an instance of B can be converted to a const A&, which is what assignment operators and copy-constructors expect their arguments to be.

The benign case

B b;
A a = b;

Nothing bad happens there - you asked for an instance of A which is a copy of B, and that's exactly what you get. Sure, a won't contain some of b's members, but how should it? It's an A, after all, not a B, so it hasn't even heard about these members, let alone would be able to store them.

The treacherous case

B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!

You might think that b2 will be a copy of b1 afterward. But, alas, it's not! If you inspect it, you'll discover that b2 is a Frankensteinian creature, made from some chunks of b1 (the chunks that B inherits from A), and some chunks of b2 (the chunks that only B contains). Ouch!

What happened? Well, C++ by default doesn't treat assignment operators as virtual. Thus, the line a_ref = b1 will call the assignment operator of A, not that of B. This is because, for non-virtual functions, the declared (formally: static) type (which is A&) determines which function is called, as opposed to the actual (formally: dynamic) type (which would be B, since a_ref references an instance of B). Now, A's assignment operator obviously knows only about the members declared in A, so it will copy only those, leaving the members added in B unchanged.

A solution

Assigning only to parts of an object usually makes little sense, yet C++, unfortunately, provides no built-in way to forbid this. You can, however, roll your own. The first step is making the assignment operator virtual. This will guarantee that it's always the actual type's assignment operator which is called, not the declared type's. The second step is to use dynamic_cast to verify that the assigned object has a compatible type. The third step is to do the actual assignment in a (protected!) member assign(), since B's assign() will probably want to use A's assign() to copy A's, members.

class A {
public:
  virtual A& operator= (const A& a) {
    assign(a);
    return *this;
  }

protected:
  void assign(const A& a) {
    // copy members of A from a to this
  }
};

class B : public A {
public:
  virtual B& operator= (const A& a) {
    if (const B* b = dynamic_cast<const B*>(&a))
      assign(*b);
    else
      throw bad_assignment();
    return *this;
  }

protected:
  void assign(const B& b) {
    A::assign(b); // Let A's assign() copy members of A from b to this
    // copy members of B from b to this
  }
};

Note that, for pure convenience, B's operator= covariantly overrides the return type, since it knows that it's returning an instance of B.

Hatter answered 22/1, 2013 at 15:0 Comment(31)
Then the some operations on an object of A are not allowed when the object is of type B.Mullinax
IMHO, the problem is that there are two different kinds of substitutability that may be implied by inheritance: either any derived value may be given to code expecting a base value, or any derived reference may be used as a base reference. I would like to see a language with a type system which addresses both concepts separately. There are many cases where a derived reference should be substitutable for a base reference, but derived instances should not be substitutable for base ones; there are also many cases where instances should be convertible but references should not substitute.Necrotomy
Conceptually, in .NET, if a function returns KeyValuePair<SiameseCat, ToyotaPrius>, one should be able to store that result into a storage location of type KeyValuePair<Animal, Vehicle>, not because an instance of the former is an instance of the latter, but rather because interpreting the return value as a KVP<A,V> would effectively turn it into one. Unfortunately, that would require a separate hierarchy from the normal inheritance one, since a boxed instance of the former type is definitely not equivalent to a boxed instance of the latter.Necrotomy
I don't understand what is so bad in your "treacherous" case. You stated that you want to: 1) get a reference to an object of class A and 2) cast the object b1 to class A and copy its stuff to a reference of the class A. What is actually wrong here is the proper logic behind the given code. In other words, you took a small image frame (A), placed it over a bigger image (B) and you painted through that frame, complaining later that your bigger image now looks ugly :) But if we just consider that framed area, it looks pretty good, just as the painter wanted, right? :)Quackenbush
The problem is, differently put, that C++ by default assumes a very strong kind of substitutability - it requires the base class'es operations to workly correctly on subclass instances. And that even for operations which the compiler autogenerated like assignment. So it's not enough to not screw up your own operations in this regard, you also have to explicitly disable the wrong ones generated by the compiler. Or of course, stay away from public inheritance, which usually is a good suggestion anway ;-)Hatter
@Necrotomy I'm a bit unclear on the distinction you're making. Is it 1) transparently passing a derived as a substitute for a base vs 2) opaquely passing a reference to a derived as though it were in fact a reference to a base?Krystinakrystle
In .NET, every structure type has an associated heap object type to which it is implicitly convertible. If the structure type implements an interface, methods of that interface may be invoked upon the structure "in place". If one had a structure FooStruct<T> with a field of type T implement interface IFoo<T> containing property Foo of type T that could read and write the field, then if FooStruct<Cat> inherited from FooStruct<Animal>, it should implement IFoo<Animal>, implying that one should be able to store an Animal to it.Necrotomy
Storing a FooStruct<Cat> into a variable of type FooStruct<Animal> would turn it into a FooStruct<Animal>, which could hold an Animal in its field, but casting it to IFoo<Animal> would turn it into the heap object type associated with FooStruct<Cat>, which would have to implement IFoo<Animal>, but couldn't accept a non-cat Animal stored into its property.Necrotomy
Another common approach is to simply disable the copy and assignment operator. For classes within inheritance hierarchy, usually there is no reason to use value instead of reference or pointer.Landloper
@Necrotomy " if FooStruct<Cat> inherited from FooStruct<Animal>". Err, what? How can structure inherit from itself in .Net? You write some very strange things that seem to be not real.Linskey
@Ark-kun: I was trying to indicate the problem that would occur if such a thing were allowed. If there were a means of declaring a structure type which could not be boxed "as its own type" [it could still be "boxed" by storing it into a single-element array], that would make it possible for .NET to support inheritance scenarios which are at present impeded by the fact that for every structure type, the Runtime also effectively defines a heap-object type which has the same inheritance relationships. Any relationships that can't work with heap objects, can't work with structs either.Necrotomy
@Linskey It wouldn't inherit from itself, FooStruct<Cat> and FooStruct<Animal> aren't the same type. You can't so such a thing in Java, because the two are, as far as the runtime is concerned, the same type. But in e.g. C++, the two are completely distinct types. You should be able to use template specializations to let one specialized type (in this case FooStruct<Cat>) from another (in this case FooStruct<Animal>). Not sure about .NET, though - it does have template specialization AFAIK, but not quite as general ones as C++ has, I think.Hatter
The "treacherous" case is indeed treacherous and I hadn't considered it before. But what you call "benign" is not benign at all -- nearly 100% of the time, it's a mistake to write this code, and the language should have been designed so that it produces a compilation error. The LSP means that a derived object should behave properly when treated as a base-class object, but "behaviour" only means "response to public method calls". The LSP permits internal state (what operator=() copies) to be arbitrarily redefined in B, so copying this to an A instance could even produce UB.Coles
@Coles I don't agree. The behaviour of C++ is very sensible for a language which has complex by-value types and inheritance. I agree that it might be surprising if you come from a language in which objects always have by-reference behaviour like Java or .NET, but learning about these kinds of things is just part of learning to code effectively in C++. I don't see how A a = b could prodce UB that couldn't also happen for B b2 = b.Hatter
@Coles For a maybe more convincing argument as to why C++ has to prevent the benign case, consider the signature of A's assignment operator - its A& operator=(const A&). So the only way to avoid that operator being with the argument being an instance of B would be to not automatically cast instances of B to const A&. But this would interfere with the LSP...Hatter
If one or more data members of A are repurposed by B, UB can easily follow from A a = b. (You could say that this is bad design/implementation, but it doesn't violate the LSP provided public methods continue to "behave properly", and can often be useful.) As a concrete example, suppose A contains two int members x and y, an int* p, sets p = &x in its ctor, and requires the expression *p to be valid at all times. (Maybe other code in A sometimes does p = &y.) B adds a third int member z, and in its ctor sets p = &z. After A a = b and b's lifetime ends, a.p no longer points to anything.Coles
I agree that it's a tricky problem. I think the right way would have been to make a distinction at the type level between types that grant only public access to an object's public methods (and which must therefore obey the LSP) and types that grant access to internal state. Most methods would need to take only the former as parameters (and would thereby be useable with an entire class hierarchy), while assignment operators and copy ctors would require the latter as their parameter (and not obey the LSP). But I may be overlooking things.Coles
How did you reassign a reference?Watanabe
@Watanabe If a is a reference, a = b does not make "a" reference object "b" (like "a = &b" would, if "a" were a pointer)! It invokes the assignment operator of the object referenced by "a", which (usually) will proceed to overwrite the referenced object with the contents of object "b".Hatter
Ah I see, yes, this answer explains it well too: https://mcmap.net/q/48113/-can-we-reassign-the-reference-in-cWatanabe
Many answers here, this one in particular, explains well what object slicing is. But given the popularity of this Q&A, it could be appropriate to also mention that one could avoid this issue altogether by making certain that non-leaf base classes should be abstract (see e.g. Item 33 in Scott Meyers _More Effective C++).Lemuel
I performed a test on MSVC 2015, and at the end a_ref is exactly equal to b1. If a_ref is a frankestinian object, is hard to say, because it looks like fully legal reference to an object of type A, with b1 content.Eradis
@Eradis Sure, if you compare a_ref and b1 as instances of A, you won't notice anything because their A-part is indeed identical. But if you compare b1 and b2, you will notice that their A-part is identical, but whatever members are added by B still have their original values.Hatter
I recently wanted to create a status struct (StatusB) that contained statuses from struct StatusA as well as some aditional statuses. I decided to derive StatusB from StatusA, but could not come up with a way to update StatusB with data from StatusA without writing my own copy constructors. This seems like a neat solution to my problem!Temper
"his is because for non-virtual functions, the declared type (which is A&) determines which function is called" - but only for functions which override base' virtual function and assignment operator in form T& op=(T&) is not one of them.Tbar
@ j_random_hacker: Your example is bad - you have to implement a copy constructor manually for such an a, and the obvious implementation that makes A a1 = a2 safe also makes A a = b safe. see herePhysician
this is terrible. i have to manually fix "assign" methods if my class layout changes.Paul
@M.kazemAkhgary Well, that's life. And it's actually not terrible, because you only have to change a class' own assign method if the class gains a new member. So it's not worse than any other class with overridden assignment or comparison operators, all of which typically must exhaustively handle all member variables.Hatter
This still doesn't show the problem of slicing. You get exactly what you asked for ... You don't have undefined behavior. It might be surprising, yes, but many other things are as well. I think a function call would much better show the problem.Bottleneck
@Bottleneck The "problem" with slicing is not that it causes undefined behavior in itself. The problem is that it's a form of non-location interaction of code from different classes that is often not considered when a class hierarchy is designed. Many C++ programmers will assume that to maintain an certain invariant in class B it suffices to main that invariant in all non-const members of B itself. But if the invariant makes assumptions about members of a base class A of B that is not enough, because any non-const member of A (like, operator= above) can be used to modify an instance of B.Hatter
I've seen something like that in a naïve swap function like void swap(Base& b1, Base& b2) using just direct object assignments and a temp object. When doing a swap(derived1, derived2);, the members of Derived not in Base were not swapped. All that went into production without even a compiler warning.Delbert
I
181

If You have a base class A and a derived class B, then You can do the following.

void wantAnA(A myA)
{
   // work with myA
}

B derived;
// work with the object "derived"
wantAnA(derived);

Now the method wantAnA needs a copy of derived. However, the object derived cannot be copied completely, as the class B could invent additional member variables which are not in its base class A.

Therefore, to call wantAnA, the compiler will "slice off" all additional members of the derived class. The result might be an object you did not want to create, because

  • it may be incomplete,
  • it behaves like an A-object (all special behaviour of the class B is lost).
Implicative answered 8/11, 2008 at 11:28 Comment(15)
C++ is not Java! If wantAnA (as its name implies!) wants an A, then that's what it gets. And an instance of A, will, uh, behave like an A. How is that surprising?Hatter
@fgp: It's surprising, because you don't pass an A to the function.Implicative
@Black: But wantAnA said it wants an A, so that's what it gets. It's the same as declaring a function to take an int, passing 0.1, and then complaining that the function receives 0...Hatter
@fgp: The behaviour is similar. However, to the average C++ programmer it might be less obvious. As far as I understood the question, nobody is "complaining". It's just about how the compiler handles the situation. Imho, it is better to avoid slicing at all by passing (const) references.Implicative
@Black: This, IMHO, is dangerous advice. If the value is later copied, passing by reference prevents move semantics from kicking in, because even if the value was a rvalue initially, it'll be an lvalue inside the function.Hatter
So you'd throw out inheritance, in favour of 'move' semantics? I'd be surprised at this being considered a general "design preference", frankly.Aquiver
@ThomasW No, I would not throw out inheritance, but use references. If the signature of wantAnA would be void wantAnA(const A & myA), then there had been not slicing. Instead, a read-only reference to the caller's object is passed.Implicative
the problem is mostly on the automatic casting that the compiler performs from derived to the type A. Implicit casting is always a source of unexpected behavior in C++, because it is often hard to understand from looking at the code locally that a cast took place.Swacked
@Black, did you mean "Imho, it's better to avoid slicing at all by passing (const) pointers" rather than "references"? GCC & clang print baseClass twice... #include <iostream> using namespace std; struct baseClass { void print() const { cout << "baseClass"; } }; struct derivedClass : public baseClass { void print() const { cout << "derivedClass"; } }; void myFunction(const baseClass& theClass) { theClass.print(); } int main() { baseClass base; myFunction(base); derivedClass derived; myFunction(derived); }Unaccomplished
@Unaccomplished For a function to be overloaded in a derived class, you have to make it "virtual", e.g., virtual void print() const {....}Implicative
@Swacked it's not a cast. An A is constructed at the call site, via a call to A::A(const A &)Ceratodus
@Ceratodus I was referring to the automatic conversion of const derived& into const A& that happens implicitly when you copy initialise an instance of A. My legalese wasn't up to standard 5 years ago so I used to wrote "cast" to identify all conversions, not only the explicit cast expressionSwacked
@Swacked there is no B&, there is no conversion, the (copy constructor's) reference (argument) is bound to the A sub-object of the B directly.Ceratodus
@Ceratodus the expression derived in the expression wantAnA(derived) is an lvalue reference, typed as B& because derived is an identifier corresponding to a non-const variable of type B. The language automatically converts the reference from B& into const A& as in const_cast<const A&>(static_cast<A&>(derived)) which then allows it to invoke A(A const &) copy constructor of the A class. If such copy constructor is aware of class B and tries to infer the actual type of its argument (i.e., by trying to dynamic_cast<const B&> its argument) it can access the whole B objectSwacked
@Hatter I don't expect that in OOP.Paul
M
57

These are all good answers. I would just like to add an execution example when passing objects by value vs by reference:

#include <iostream>

using namespace std;

// Base class
class A {
public:
    A() {}
    A(const A& a) {
        cout << "'A' copy constructor" << endl;
    }
    virtual void run() const { cout << "I am an 'A'" << endl; }
};

// Derived class
class B: public A {
public:
    B():A() {}
    B(const B& a):A(a) {
        cout << "'B' copy constructor" << endl;
    }
    virtual void run() const { cout << "I am a 'B'" << endl; }
};

void g(const A & a) {
    a.run();
}

void h(const A a) {
    a.run();
}

int main() {
    cout << "Call by reference" << endl;
    g(B());
    cout << endl << "Call by copy" << endl;
    h(B());
}

The output is:

Call by reference
I am a 'B'

Call by copy
'A' copy constructor
I am an 'A'
Misdeal answered 22/8, 2014 at 18:33 Comment(2)
Hello. Great answer but I have one question. If I do something like this ** dev d; base* b = &d;** The slicing also takes place?Nitride
@Nitride If you introduce some new member functions or member variables in the derived class then those are not accessible from the base class pointer directly. However you can still access them from inside the overloaded base class virtual functions. See this: godbolt.org/z/LABx33Worried
M
36

Third match in google for "C++ slicing" gives me this Wikipedia article http://en.wikipedia.org/wiki/Object_slicing and this (heated, but the first few posts define the problem) : http://bytes.com/forum/thread163565.html

So it's when you assign an object of a subclass to the super class. The superclass knows nothing of the additional information in the subclass, and hasn't got room to store it, so the additional information gets "sliced off".

If those links don't give enough info for a "good answer" please edit your question to let us know what more you're looking for.

Mcgurn answered 8/11, 2008 at 11:14 Comment(0)
F
34

The slicing problem is serious because it can result in memory corruption, and it is very difficult to guarantee a program does not suffer from it. To design it out of the language, classes that support inheritance should be accessible by reference only (not by value). The D programming language has this property.

Consider class A, and class B derived from A. Memory corruption can happen if the A part has a pointer p, and a B instance that points p to B's additional data. Then, when the additional data gets sliced off, p is pointing to garbage.

Flounce answered 8/11, 2008 at 11:56 Comment(9)
Please explain how the memory corruption can occur.Abound
Given: class A { virtual void foo() { } }; class B : A { int *p; void foo() { *p = 3; } }; Now, slice a B when it is assigned to A, call foo(), which calls B::foo(), and voila! Memory corruption assigning through garbage value for B::p.Flounce
Yup, Walter is mixing pointer assignment (where A* points to a B object, so B::p is NOT lost) and object assignment (after which A::foo is called).Darendaresay
I forgot that the copy ctor will reset the vptr, my mistake. But you can still get corruption if A has a pointer, and B sets that to point into B's section that gets sliced off.Flounce
This problem isn't just limited to slicing. Any classes that contain pointers are going to have dubious behaviour with a default assignment operator and copy-constructor.Simonne
@Simonne - Which is why you override the default destructor, assignment operator and copy-constructor in these cases.Provo
Polymorphism requires a virtual function or CRTP (templates to make it happen at compile time)...Lot
@Weeble: What makes object slicing worse than general pointer fixups is that to be certain you have prevented slicing from happening, a base class must provide converting constructors for every derived class. (Why? Any derived classes that are missed are susceptible to being picked up by the base class's copy ctor, since Derived is implicitly convertible to Base.) This is obviously counter to the Open-Closed Principle, and a big maintenance burden.Coles
This is one of most frustrating answers on C++ SO. This has no example, really vague and simply false: slicing normally will not cause "memory corruption" (can you please, elaborate the term?), i do not see a case where it will damage stack, or anything sensitive to execution. It can, however, be a programmer error in his own application, but it is not "corruption". Finally, most assignments like this will be done via pointer, without slicing, the pattern from OP is never discussed in most popular references.Tieratierce
C
14

I see all the answers mention when object slicing happens when data members are sliced. Here I give an example that the methods are not overridden:

class A{
public:
    virtual void Say(){
        std::cout<<"I am A"<<std::endl;
    }
};

class B: public A{
public:
    void Say() override{
        std::cout<<"I am B"<<std::endl;
    }
};

int main(){
   B b;
   A a1;
   A a2=b;

   b.Say(); // I am B
   a1.Say(); // I am A
   a2.Say(); // I am A   why???
}

B (object b) is derived from A (object a1 and a2). b and a1, as we expect, call their member function. But from polymorphism viewpoint we don’t expect a2, which is assigned by b, to not be overridden. Basically, a2 only saves A-class part of b and that is object slicing in C++.

To solve this problem, a reference or pointer should be used

 A& a2=b;
 a2.Say(); // I am B

or

A* a2 = &b;
a2->Say(); // I am B
Coyotillo answered 12/9, 2020 at 11:27 Comment(1)
But from polymorphism viewpoint we don’t expect a2, which is assigned by b, to not be overridden. Basically, a2 only saves A-class part of b and that is object slicing in C++. This doesn't sound right and isn't clear. a2's static type is A which is why A::Say() was invoked -- I don't think it has to do with object slicing.Kelt
N
13

In C++, a derived class object can be assigned to a base class object, but the other way is not possible.

class Base { int x, y; };

class Derived : public Base { int z, w; };

int main() 
{
    Derived d;
    Base b = d; // Object Slicing,  z and w of d are sliced off
}

Object slicing happens when a derived class object is assigned to a base class object, additional attributes of a derived class object are sliced off to form the base class object.

Nusku answered 7/3, 2018 at 9:35 Comment(0)
C
8

The slicing problem in C++ arises from the value semantics of its objects, which remained mostly due to compatibility with C structs. You need to use explicit reference or pointer syntax to achieve "normal" object behavior found in most other languages that do objects, i.e., objects are always passed around by reference.

The short answers is that you slice the object by assigning a derived object to a base object by value, i.e. the remaining object is only a part of the derived object. In order to preserve value semantics, slicing is a reasonable behavior and has its relatively rare uses, which doesn't exist in most other languages. Some people consider it a feature of C++, while many considered it one of the quirks/misfeatures of C++.

Contralto answered 9/11, 2008 at 0:31 Comment(4)
""normal" object behavior" that's not "normal object behaviour", that's reference semantic. And it relates in no way with C struct, compatibility, or other non-sense the any random OOP priest told you.Mullinax
@Mullinax Amen, brother. It's sad to see how often C++ get bashed from not being Java, when value semantics is one of the things that makes C++ so insanely powerfull.Hatter
This is not a feature, not a quirk/misfeature. It is normal on-stack-copying behavior, since calling a function with an arg or (same) allocating stack variable of type Base must take exactly sizeof(Base) bytes in memory, with possible alignment, maybe, that's why "assignment" (on-stack-copy) will not copy derived class members, their offsets are outside sizeof. To avoid "losing data", just use pointer, like anyone else, since pointer memory is fixed in place and size, whereas stack is very volitileTieratierce
Definitely a misfeature of C++. Assigning a derived object to a base object should be banned, while binding a derived object to a reference or a pointer of the base class should be OK.Noleta
K
7

So ... Why is losing the derived information bad? ... because the author of the derived class may have changed the representation such that slicing off the extra information changes the value being represented by the object. This can happen if the derived class if used to cache a representation that is more efficient for certain operations, but expensive to transform back to the base representation.

Also thought someone should also mention what you should do to avoid slicing... Get a copy of C++ Coding Standards, 101 rules guidlines, and best practices. Dealing with slicing is #54.

It suggests a somewhat sophisticated pattern to fully deal with the issue: have a protected copy constructor, a protected pure virtual DoClone, and a public Clone with an assert which will tell you if a (further) derived class failed to implement DoClone correctly. (The Clone method makes a proper deep copy of the polymorphic object.)

You can also mark the copy constructor on the base explicit which allows for explicit slicing if it is desired.

Knipe answered 8/11, 2008 at 17:38 Comment(1)
"You can also mark the copy constructor on the base explicit" which does not help at all.Mullinax
H
5

1. THE DEFINITION OF SLICING PROBLEM

If D is a derived class of the base class B, then you can assign an object of type Derived to a variable (or parameter) of type Base.

EXAMPLE

class Pet
{
 public:
    string name;
};
class Dog : public Pet
{
public:
    string breed;
};

int main()
{   
    Dog dog;
    Pet pet;

    dog.name = "Tommy";
    dog.breed = "Kangal Dog";
    pet = dog;
    cout << pet.breed; //ERROR

Although the above assignment is allowed, the value that is assigned to the variable pet loses its breed field. This is called the slicing problem.

2. HOW TO FIX THE SLICING PROBLEM

To defeat the problem, we use pointers to dynamic variables.

EXAMPLE

Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;         
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed; 

In this case, none of the data members or member functions of the dynamic variable being pointed to by ptrD (descendant class object) will be lost. In addition, if you need to use functions, the function must be a virtual function.

Herrmann answered 28/1, 2012 at 18:0 Comment(6)
I understand the "slicing" part, but I don't understand "problem". How is it a problem that some state of dog that isn't part of class Pet (the breed data member) isn't copied in the variable pet? The code is is only interested in the Pet data members - apparently. Slicing is definitely a "problem" if it is unwanted, but I don't see that here.Mullinax
"((Dog *)ptrP)" I suggest using static_cast<Dog*>(ptrP)Mullinax
I suggest pointing out that you will make the string 'breed' eventually leak memory without a virtual destructor (the destructor of 'string' will not be called) when deleting through 'ptrP'... Why is what you show problematic? The fix is mostly proper class design. The problem in this case is that writing down constructors to control visibility when inheriting is tedious and easily forgotten. You won't get anywhere near the danger zone with your code as there is no polymorphism involved or even mentioned (slicing will truncate your object but not make your program crash, here).Lot
-1 This completely fails to explain the actual problem. C++ has value semantics, not reference semantics like Java, so this is all entirely to be expected. And the "fix" really is an example of truely horrible C++ code. "Fixing" non-existing problems like this type of slicing by resorting to dynamic allocation is a recipe for buggy code, leaked memory and horrible performance. Note that there are cases where slicing is bad, but this answer failes to point them out. Hint: the trouble starts if you assign through references.Hatter
Do you even understand that trying to access member of type that is not defined (Dog::breed) is no way an ERROR related to SLICING?Tieratierce
Have to give a -1, This is a compile-time error, not a run-time error, Pet::breed does not exist.Ewing
J
4

It seems to me, that slicing isn't so much a problem other than when your own classes and program are poorly architected/designed.

If I pass a subclass object in as a parameter to a method, which takes a parameter of type superclass, I should certainly be aware of that and know the internally, the called method will be working with the superclass (aka baseclass) object only.

It seems to me only the unreasonable expectation that providing a subclass where a baseclass is requested, would somehow result in subclass specific results, would cause slicing to be a problem. Its either poor design in the use of the method or a poor subclass implementation. I'm guessing its usually the result of sacrificing good OOP design in favor of expediency or performance gains.

Jesus answered 24/7, 2009 at 19:45 Comment(4)
But remember, Minok, that you're NOT passing in a reference of that object. You're passing a NEW copy of that object, but using the base class to copy it in the process.Kronstadt
protected copy/assignment on the base class and this problem is solved.Lot
You're right. Good practice is to use abstract base classes or to restrict the access to copy/assignment. However, it's not so easy to spot once it's there and easy to forget to take care of. Calling virtual methods with sliced *this can make mysterious things happen if you get away without an access violation.Lot
I recall from my C++ programming courses in university that there were standing best practices that for every class we created, we were required to write default constructors, copy constructors and assignment operators, as well as a destructor. This way you made sure that copy construction and the like happened the way you needed it to, while writing the class... rather than later on some odd behavior showing up.Jesus
L
4

OK, I'll give it a try after reading many posts explaining object slicing but not how it becomes problematic.

The vicious scenario that can result in memory corruption is the following:

  • Class provides (accidentally, possibly compiler-generated) assignment on a polymorphic base class.
  • Client copies and slices an instance of a derived class.
  • Client calls a virtual member function that accesses the sliced-off state.
Lot answered 18/10, 2012 at 3:22 Comment(1)
If the client calls a virtual method on the (copied-into) base-class object, the virtual method that is called will be the one defined in the base class, not the virtual method defined in the subclass. Or to put it another way, the assignment-operator will not modify the base-class object's vtable-pointer.Guest
P
3

Slicing means that the data added by a subclass are discarded when an object of the subclass is passed or returned by value or from a function expecting a base class object.

Explanation: Consider the following class declaration:

           class baseclass
          {
                 ...
                 baseclass & operator =(const baseclass&);
                 baseclass(const baseclass&);
          }
          void function( )
          {
                baseclass obj1=m;
                obj1=m;
          }

As baseclass copy functions don't know anything about the derived only the base part of the derived is copied. This is commonly referred to as slicing.

Preston answered 12/3, 2014 at 18:8 Comment(0)
P
-1
class A 
{ 
    int x; 
};  

class B 
{ 
    B( ) : x(1), c('a') { } 
    int x; 
    char c; 
};  

int main( ) 
{ 
    A a; 
    B b; 
    a = b;     // b.c == 'a' is "sliced" off
    return 0; 
}
Poulenc answered 29/11, 2012 at 12:32 Comment(2)
Would you mind giving some extra details? How does your answer differ from the already posted ones?Greengrocery
I guess that more explanation wouldn't be bad.Ethnography
C
-1

when a derived class object is assigned to a base class object, additional attributes of a derived class object are sliced off (discard) form the base class object.

class Base { 
int x;
 };

class Derived : public Base { 
 int z; 
 };

 int main() 
{
Derived d;
Base b = d; // Object Slicing,  z of d is sliced off
}
Catboat answered 11/5, 2016 at 7:39 Comment(0)
P
-1

When a Derived class Object is assigned to Base class Object, all the members of derived class object is copied to base class object except the members which are not present in the base class. These members are Sliced away by the compiler. This is called Object Slicing.

Here is an Example:

#include<bits/stdc++.h>
using namespace std;
class Base
{
    public:
        int a;
        int b;
        int c;
        Base()
        {
            a=10;
            b=20;
            c=30;
        }
};
class Derived : public Base
{
    public:
        int d;
        int e;
        Derived()
        {
            d=40;
            e=50;
        }
};
int main()
{
    Derived d;
    cout<<d.a<<"\n";
    cout<<d.b<<"\n";
    cout<<d.c<<"\n";
    cout<<d.d<<"\n";
    cout<<d.e<<"\n";


    Base b = d;
    cout<<b.a<<"\n";
    cout<<b.b<<"\n";
    cout<<b.c<<"\n";
    cout<<b.d<<"\n";
    cout<<b.e<<"\n";
    return 0;
}

It will generate:

[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'
Popeyed answered 19/7, 2017 at 8:2 Comment(2)
Downvoted because that's not a good example. It wouldn't work either if instead of copying d to b, you would use a pointer in which case d and e would still exist but Base doesn't have those members. Your example only shows that you can't access members that the class doesn't have.Nansen
Why should I not #include <bits/stdc++.h>?, Why is “using namespace std;” considered bad practice?Gaberones

© 2022 - 2024 — McMap. All rights reserved.