专门化模板覆盖函数/避免对象切片

Specializing a templated overriden function / Avoid object slicing

本文关键字:对象 切片 函数 覆盖 专门化      更新时间:2023-10-16

我已经开始编写一些类来计算沿着运动链的变换。我有一个模板化的父类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的非协方差,但在网站上有关于它的问题。