了解对象切片

时间:2017-10-20 09:38:10

标签: c++

要理解对象切片的问题,我想我已经创建了一个可怕的例子,我试图测试它。然而,这个例子并没有我想象的那么糟糕。

下面是一个最小的工作示例,如果您帮助我理解为什么它仍然“正常工作”,我将不胜感激。如果你帮助我让这个例子变得更糟,那会更好。

#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;
}

编译代码时得到的输出如下:

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

我希望最后一行抛出一些异常或者只是失败。当c的基本部分在indirect中被切片时,lambda表达式中没有this->val使用(我知道,C ++是静态编译的语言,而不是动态语言) 。我还尝试在复制分配this->val时按值捕获Base<T>::f,但它没有更改结果。

基本上,我的问题是双重问题。首先,这是未定义的行为,还是仅仅是一个合法的代码?其次,如果这是合法代码,为什么行为不受切片影响?我的意思是,我可以看到从T func1() const部分调用Base<T>,但为什么捕获的值不会造成任何麻烦?

最后,我如何构建一个示例来产生更糟糕的副作用,例如内存访问类型的问题?

提前感谢您的时间。

编辑。我知道另一个标记为重复的主题。我已经阅读了那里的所有帖子,事实上,我一直在尝试复制the last post。正如我上面提到的,我正试图获得行为

  

然后b中有关成员栏的信息丢失在。

我无法完全得到。对我来说,只有部分信息似乎丢失了。基本上,在上一篇文章中,该人声称

  

来自实例的额外信息已丢失,f现在容易出现未定义的行为。

在我的例子中,f似乎也在起作用。相反,我只是打电话给T Base<T>::func1() const,这并不奇怪。

1 个答案:

答案 0 :(得分:1)

您当前的代码没有未定义的行为。但是,它很危险,因此很容易用它做出不确定的行为。

切片发生,但您访问this->val。看起来像魔术一样,但您只是从主人this->val访问Child<double> c

这是因为lambda捕获。您捕获this,它指向您主要的c变量。然后,将该lambda分配到基类内的std::function。您的基类现在有一个指向c变量的指针,以及通过val访问std::function的方法。

因此会发生切片,但您可以访问未切割的对象。

这也是数字不能乘以2的原因。虚拟调用解析为base,并且主valc的值为10

您的代码大致相当于:

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;
}
相关问题