了解对象切片

Understanding object slicing

本文关键字:切片 对象 了解      更新时间:2023-10-16

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

下面是一个最小的工作示例,如果你能帮助我理解为什么它仍然"正常工作",我将不胜感激。如果你帮我把这个例子做得更糟,那就更好了。

#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++是一种静态编译的语言,而不是动态编译的语言)。我还尝试过在复制分配Base<T>::f时按值捕获this->val,但它并没有改变结果。

基本上,我的问题有两个方面。首先,这是一种不明确的行为,还是仅仅是一种法律规范?第二,如果这是一部法律法规,为什么切片不会影响行为?我的意思是,我可以看到T func1() const是从Base<T>部分调用的,但为什么捕获的值没有引起任何麻烦?

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

提前感谢您抽出时间。

编辑我知道另一个被标记为重复的主题。我已经阅读了那里的所有帖子,事实上,我一直在努力复制那里的最后一篇帖子。正如我上面所问的,我正在努力获得行为

然后b中关于会员栏的信息在a中丢失。

我无法完全理解。对我来说,似乎只丢失了部分信息。基本上,在最后一篇文章中,此人声称

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

在我的示例中,f似乎也能正常工作。相反,我只是接到了T Base<T>::func1() const的电话,这并不奇怪。

当前代码中没有未定义的行为。然而,它是危险的,因此很容易做出不明确的行为

切片发生了,但是您访问了this->val。看起来很神奇,但你只是从主菜单的Child<double> c访问this->val

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

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

这也是为什么这个数字不乘以2的原因。虚拟调用解析为base,并且主中cval的值为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;
}