简体   繁体   中英

Understanding object slicing

To understand the problems with object slicing, I thought I have created a horrible example and I was trying to test it. However, the example is not as bad as I thought it would be.

Below is a minimal working example, and I would appreciate if you helped me understand why it is still "working properly". It would be even better if you helped me make the example worse.

#include <functional>
#include <iostream>

template <class T> class Base {
protected:
  std::function<T()> f; // inherited

public:
  Base() : f{[]() { return T{0}; }} {} // initialized
  virtual T func1() const { return f(); }
  virtual ~Base() = default; // avoid memory leak for children
};

template <class T> class Child : public Base<T> {
private:
  T val;

public:
  Child() : Child(T{0}) {}
  Child(const T &val) : Base<T>{}, val{val} { // initialize Base<T>::f
    Base<T>::f = [&]() { return this->val; }; // copy assign Base<T>::f
  }
  T func1() const override { return T{2} * Base<T>::f(); }
  void setval(const T &val) { this->val = val; }
};

template <class T> T indirect(const Base<T> b) { return b.func1(); }

int main(int argc, char *argv[]) {
  Base<double> b;
  Child<double> c{5};

  std::cout << "c.func1() (before): " << c.func1() << '\n'; // as expected
  c.setval(10);
  std::cout << "c.func1() (after): " << c.func1() << '\n'; // as expected

  std::cout << "indirect(b): " << indirect(b) << '\n'; // as expected
  std::cout << "indirect(c): " << indirect(c) << '\n'; // not as expected

  return 0;
}

The output I get when I compile the code is as follows:

c.func1() (before): 10
c.func1() (after): 20
indirect(b): 0
indirect(c): 10

I would expect the last line to throw some exception or simply fail. When the base part of c gets sliced in indirect , there is no this->val to be used inside the lambda expression (I know, C++ is a statically compiled language, not a dynamic one). I have also tried capturing this->val by value when copy assigning Base<T>::f , but it did not change the result.

Basically, my question is two folds. First, is this undefined behaviour, or simply a legal code? Second, if this is a legal code, why is the behaviour not affected by slicing? I mean, I can see that T func1() const is called from the Base<T> part, but why is the captured value not causing any trouble?

Finally, how can I build an example to have worse side-effects such as memory access type of problems?

Thank you in advance for your time.

EDIT. I am aware of the other topic that has been marked as duplicate. I have read all the posts there, and in fact, I have been trying to duplicate the last post there. As I have asked above, I am trying to get the behaviour

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

which I cannot get fully. To me, only partial information seems to be lost. Basically, in the last post, the person claims

The extra information from the instance has been lost, and f is now prone to undefined behaviour.

In my example, f seems to be working just as well. Instead, I just have the call to T Base<T>::func1() const , which is no surprise.

There is no undefined behavior with your current code. However, it's dangerous and therefore easy to make undefined behavior with it.

The slicing happen, and yet you access this->val . Seems like magic, but you're just accessing the this->val from Child<double> c from your main!

That's because of the lambda capture. You capture this , which points to your c variable in your main. You then assign that lambda into a std::function inside your base class. You base class now have a pointer to the c variable, and a way to access the val through the std::function .

So the slicing occurs, but you access to the unsliced object.

This is also why the number is not multiplied by two. The virtual call resolves to base, and the value of val in c in your main is 10 .

Your code is roughly equivalent to that:

struct B;

struct A {
    B* b = nullptr;

    int func1() const;
};

struct B : A {
    int val;
    explicit B(int v) : A{this}, val{v} {}
};

int A::func1() const {
    return b->val;
}

int main() {
    B b{10};

    A a = b;

    std::cout << a.func1() << std::endl;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM