要理解对象切片的问题,我想我已经创建了一个可怕的例子,我试图测试它。然而,这个例子并没有我想象的那么糟糕。
下面是一个最小的工作示例,如果您帮助我理解为什么它仍然“正常工作”,我将不胜感激。如果你帮助我让这个例子变得更糟,那会更好。
#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
,这并不奇怪。
答案 0 :(得分:1)
您当前的代码没有未定义的行为。但是,它很危险,因此很容易用它做出不确定的行为。
切片发生,但您访问this->val
。看起来像魔术一样,但您只是从主人this->val
访问Child<double> c
!
这是因为lambda捕获。您捕获this
,它指向您主要的c
变量。然后,将该lambda分配到基类内的std::function
。您的基类现在有一个指向c
变量的指针,以及通过val
访问std::function
的方法。
因此会发生切片,但您可以访问未切割的对象。
这也是数字不能乘以2的原因。虚拟调用解析为base,并且主val
中c
的值为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;
}