了解对象切片
Understanding object slicing
为了理解对象切片的问题,我认为我创建了一个可怕的例子,并试图测试它。然而,这个例子并没有我想象的那么糟糕。
下面是一个最小的工作示例,如果你能帮助我理解为什么它仍然"正常工作",我将不胜感激。如果你帮我把这个例子做得更糟,那就更好了。
#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,并且主中c
中val
的值为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;
}
- 如何优雅地切片对象
- 避免矢量中的对象切片<Base><shared_ptr>
- 专门化模板覆盖函数/避免对象切片
- 防止在按值传递对象(继承)时进行切片
- C++ 被此代码与多态性、指针和对象切片混淆
- 我们能胜过对象切片吗?
- 如何在考虑对象切片的同时通过传入单个 Base 对象来打印出数组中的对象?
- 成员变量和对象切片
- 如何在不引入未来对象切片的情况下实现 ICloneable
- 避免对象切片
- 故意对对象切片是一种可行的技术吗?
- 对象切片:通过按值派生为 Base - 安全还是危险?
- C++ CRTP 派生类对象切片
- C++ - 即使在使用指针后也能进行对象切片
- 了解对象切片
- 使用对象切片可靠地从多个基类之一中复制
- 将其切片在继承的对象中
- 如何在boost::Python中处理Python切片对象
- std::反向提升::p tr_vector 切片对象
- 返回对切片对象(超类型)的引用