专门化模板覆盖函数/避免对象切片
Specializing a templated overriden function / Avoid object slicing
我已经开始编写一些类来计算沿着运动链的变换。我有一个模板化的父类KinematicChainSegment
和它的多个专门实现(例如,用于旋转或棱柱关节)。举一个具体的、最小的例子——我希望它看起来是这样的:
#include <Eigen/Dense>
template<typename T>
class KinematicChainSegment {
public:
KinematicChainSegment() {};
virtual ~KinematicChainSegment() {};
virtual Eigen::Matrix<T, 4, 4> getTransformationMatrix() const = 0;
virtual KinematicChainSegment<T> inverse() const = 0;
};
template<typename T>
class StaticLink : public KinematicChainSegment<T> {
public:
StaticLink(const Eigen::Matrix<T, 4, 4>& transformation = Eigen::Matrix<T, 4, 4>::Identity())
: KinematicChainSegment<T>(),
_transformationMatrix(transformation) {
}
virtual ~StaticLink() {};
virtual Eigen::Matrix<T,4,4> getTransformationMatrix() const override {
return _transformationMatrix;
}
virtual StaticLink<T> inverse() const override {
return StaticLink<T>(_transformationMatrix.inverse());
}
protected:
Eigen::Matrix<T,4,4> _transformationMatrix;
};
然而,在编译这个例子时,我得到了error: invalid abstract return type ‘KinematicChainSegment<T>’
,将返回类型StaticLink<T>::inverse()
更改为KinematicChainSegment<T>
会得到error: invalid abstract return type ‘KinematicChainSegment<T>’
,所以我最终得到了以下代码:
#include <Eigen/Dense>
#include <iostream>
template<typename T>
class KinematicChainSegment {
public:
KinematicChainSegment() {};
virtual ~KinematicChainSegment() {};
virtual Eigen::Matrix<T, 4, 4> getTransformationMatrix() const {
std::cout << "Calling KinematicChainSegment<T>::getTransformationMatrix(). This method should be overwritten by any derivative class." << std::endl;
return Eigen::Matrix<T, 4, 4>::Identity();
}
virtual KinematicChainSegment<T> inverse() const {
std::cout << "Calling KinematicChainSegment<T>::inverse(). This method should be overwritten by any derivative class." << std::endl;
return KinematicChainSegment<T>();
};
};
template<typename T>
class StaticLink : public KinematicChainSegment<T> {
public:
StaticLink(const Eigen::Matrix<T, 4, 4>& transformation = Eigen::Matrix<T, 4, 4>::Identity())
: KinematicChainSegment<T>(),
_transformationMatrix(transformation) {
}
virtual ~StaticLink() {};
virtual Eigen::Matrix<T,4,4> getTransformationMatrix() const override {
return _transformationMatrix;
}
virtual KinematicChainSegment<T> inverse() const override {
return StaticLink<T>(_transformationMatrix.inverse());
}
protected:
Eigen::Matrix<T,4,4> _transformationMatrix;
};
int main(int argc, char** argv) {
Eigen::Matrix<float,4,4> transform;
transform << 1, 0, 0, 1,
0, 1, 0, 2,
0, 0, 1, 3,
0, 0, 0, 1;
KinematicChainSegment<float> link = StaticLink<float>(transform);
std::cout << "link: " << link.getTransformationMatrix() << std::endl;
std::cout << "inverse link: " << link.inverse().getTransformationMatrix() << std::endl;
}
不幸的是,现在出现了一堆对象切片问题,正如您在下面的程序输出中看到的那样:
link: Calling KinematicChainSegment<T>::getTransformationMatrix(). This method should be overwritten by any derivative class.
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
inverse link: Calling KinematicChainSegment<T>::inverse(). This method should be overwritten by any derivative class.
Calling KinematicChainSegment<T>::getTransformationMatrix(). This method should be overwritten by any derivative class.
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
我想我对对象继承的理解可能过于Java化了。。。解决此类问题的最佳实践是什么?从我目前的发现来看,唯一的选择似乎是同时使用指针、所有返回类型和使用的对象实例。。。或者还有其他方法可以让它发挥作用吗?
使用Java知识来推理C++的事实会让你陷入无尽的麻烦,因为它们实际上是完全不同的语言。
这次您遇到了麻烦,因为Java默认使用引用语义,而C++使用值语义。从函数中按值返回意味着(除了省略的情况,我不会详细介绍)构造返回类型的实例——这对于抽象类来说是不可能的。按值返回也会导致在使基非抽象时看到的对象切片。
要解决C++中的问题,有必要使用某种形式的指针,最好是智能指针。我将使用模板化类std::unique_ptr
(在标准头<memory>
中)提供一个选项。这只适用于C++11及更高版本(因为是C++11引入了unique_ptr
)。
首先,更改inverse()
的返回类型
template<typename T>
class KinematicChainSegment
{
public:
// other member functions omitted
virtual std::unique_ptr<KinematicChainSegment<T> > inverse() const = 0;
};
然后在派生类中覆盖它
template<typename T>
class StaticLink : public KinematicChainSegment<T>
{
public:
virtual std::unique_ptr<KinematicChainSegment<T> > inverse() const override;
protected:
Eigen::Matrix<T,4,4> _transformationMatrix;
};
请注意,被重写的inverse()
的返回类型与基类中的相同。将返回类型更改为std::unique_ptr<StaticLink<T> >
将不起作用——即使StaticLink<T>
是从KinematicChainSegment<T>
派生的,但类std::unique_ptr<StaticLink<T> >
实际上并不是从std::unique_ptr<KinematicChainSegment<T> >
派生的。
虽然内联定义(实现)这个函数是可能的,但为了便于解释,我将在内联之外定义这个函数。
template<class T>
std::unique_ptr<KinematicChainSegment<T> > StaticLink<T>::inverse() const
{
return std::make_unique<StaticLink<T> >(_transformationMatrix.inverse());
}
CCD_ 16的这个调用具有构造CCD_ 17的效果,该CCD_。
这是因为std::unique_ptr
确实支持隐式转换。StaticLink<T>
继承自KinematicChainSegment<T>
,因此std::unique_ptr<StaticLink<T> >
到std::unique_ptr<KinematicChainSegment<T> >
的转换是有效的,尽管它们之间没有继承关系。
上面的内容意味着调用者将接收一个KinematicChainSegment<T>
,然后可以以多态方式使用托管的。例如
int main()
{
StaticLink<T> static_link;
// presumably set state of the object static_link here
std::unique_ptr<KinematicChainSegment<T> > clone = static_link.inverse();
// Use virtual functions of clone
std::unique_ptr<KinematicChainSegment<T> > clone_inverse = clone->inverse();
}
在这种情况下,clone
是管理指向StaticLink<T>
的指针的std::unique_ptr<KinematicChainSegment<T> >
。因此,对clone->inverse()
的调用是多态的,并且还将返回管理指向StaticLink<T>
的指针的std::unique_ptr<KinematicChainSegment<T> >
。
如果设计正确(与任何形式的继承一样,无论是否涉及模板类),main()
根本不需要对类StaticLink
有硬编码的知识,因为成员函数的调用将是多态的(解析为实际包含对象的类型)。
也可以使用原始指针(而不是智能指针)。这样做有很多不利的方面,所以我不会证明这一点。
在混合使用模板和继承时,以上内容(我认为)比您预期的要复杂。我强烈建议您不要将模板与继承混合在一起,直到您(独立地)对每种模板都有了更多的了解。
还有,回到我的开场白。如果你想学习C++,不要试图将你的Java知识映射到C++中。Java和C++实际上是完全不同的语言,即使它们的语法相似,而且它们的工作方式也完全不同。实际上,您需要忘记Java中流行的一些技术,因为它们在C++中根本无法正常工作。(同样,基于C++知识学习Java的人也会遇到问题,因为很多技术都不适用)。
您试图按值返回抽象类的对象。这个问题与使用模板无关。
在C++中,基于继承的多态性需要一个指针或引用才能工作。
一个简单的解决方案是让inverse
返回一个std::unique_ptr<KinematicChainSegment<T>>
,并在overriden函数中使用std::make_unique
。
如果您经常以非多态的方式使用对象(如您的示例中所示),则此解决方案可能并不理想,因为它会导致不必要的分配,并且需要B bf = *b.inverse()
来进行复制。
但是,可以保留这两个接口。
#include <memory>
struct A
{
virtual std::unique_ptr<A> f() const {
return this->f_impl();
}
protected:
virtual std::unique_ptr<A> f_impl() const = 0;
};
struct B : A
{
B f(int = 0) const // additional parameter so it doesn't collide with A::f
{
return B{};
}
protected:
std::unique_ptr<A> f_impl() const override
{
return std::make_unique<B>();
}
};
int main()
{
{
std::unique_ptr<A> b = std::make_unique<B>();
std::unique_ptr<A> bf = b->f();
(void)bf;
}
{
B b{};
B bf = b.f();
(void)bf;
}
}
http://coliru.stacked-crooked.com/a/c8a2423461eb8d84
如果类型在编译时已知,则返回值;如果通过基类访问,则返回std::unique_ptr
。这可以扩展到更高深度的继承层次结构,唯一的问题是std::unique_ptr
的非协方差,但在网站上有关于它的问题。
- 避免矢量中的对象切片<Base><shared_ptr>
- 专门化模板覆盖函数/避免对象切片
- C++ 被此代码与多态性、指针和对象切片混淆
- 我们能胜过对象切片吗?
- 如何在考虑对象切片的同时通过传入单个 Base 对象来打印出数组中的对象?
- 成员变量和对象切片
- 如何在不引入未来对象切片的情况下实现 ICloneable
- 避免对象切片
- 故意对对象切片是一种可行的技术吗?
- 对象切片:通过按值派生为 Base - 安全还是危险?
- C++ CRTP 派生类对象切片
- C++ - 即使在使用指针后也能进行对象切片
- 了解对象切片
- 使用对象切片可靠地从多个基类之一中复制
- 对象切片和隐式类型转换
- 一种克服对象切片的方法
- 按引用传递可避免对象切片
- 使用CRTP时的对象切片
- 对象切片:从基类对象访问分层的类方法
- 这会导致对象切片吗?