Специализация шаблонной функции переопределения / Избегайте нарезки объектов - PullRequest
1 голос
/ 17 июня 2020

Я начал писать несколько классов для вычисления преобразования по кинематической цепочке c. У меня есть шаблонный родительский класс KinematicChainSegment и несколько его специализированных реализаций (например, для вращающихся или призматических c суставов). Чтобы дать конкретный, минимальный пример - вот как я бы хотел, чтобы он выглядел:

#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 -подобное понимание наследования объектов ... Как лучше всего решить такую ​​проблему? Из того, что я нашел до сих пор, кажется, что единственный вариант - использовать как указатели, все возвращаемые типы, так и используемые экземпляры объектов ... Или есть какой-либо другой способ сделать эту работу?

Ответы [ 2 ]

2 голосов
/ 17 июня 2020

Тот факт, что вы используете свои знания Java, чтобы рассуждать о C ++, доставит вам немало неприятностей, поскольку на самом деле это совершенно разные языки.

На этот раз вы столкнулись с проблемой, потому что Java по умолчанию использует ссылочную семантику, а C ++ использует семантику значений. Возврат по значению из функции означает (кроме случаев исключения, которые я не буду расширять) создание экземпляра возвращаемого типа, что невозможно для абстрактного класса. Возврат по значению также вызывает нарезку объекта, которую вы видите, когда делаете базу не абстрактной.

Чтобы исправить проблему в C ++, необходимо использовать указатель в какой-либо форме - предпочтительно интеллектуальный указатель. Я предоставлю вариант с использованием шаблонного класса std::unique_ptr (в стандартном заголовке <memory>). Это работает только для C ++ 11 и более поздних версий (поскольку unique_ptr был введен в C ++ 11).

Во-первых, измените тип возвращаемого значения 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());
}

Этот вызов std::make_unique() создает эффект std::unique_ptr<StaticLink<T> >, который управляет объектом, созданным из _transformationMatrix.inverse().

Это работает, поскольку 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 - это std::unique_ptr<KinematicChainSegment<T> >, который управляет указателем на StaticLink<T>. Таким образом, вызов clone->inverse() является polymorphi c, а также возвращает std::unique_ptr<KinematicChainSegment<T> >, который управляет указателем на StaticLink<T>.

Если вы правильно спроектируете (как и в случае любой формы наследования, независимо от того, с использованием шаблонных классов или нет) main() вообще не обязательно иметь жестко запрограммированные знания о классе StaticLink, поскольку вызовы функций-членов будут полиморфными c (разрешить тип фактического содержащегося объекта).

Также можно использовать сырые (не интеллектуальные указатели). У этого есть множество недостатков, поэтому я не буду это демонстрировать.

Вышесказанное (я полагаю) сложнее, чем вы ожидали, когда смешиваете использование шаблонов с наследованием. Я настоятельно рекомендую не смешивать шаблоны с наследованием, пока вы не узнаете значительно больше о каждом (независимо).

Также, возвращаясь к моему открытому комментарию. Если вы хотите изучать C ++, не пытайтесь сопоставить свои знания Java с C ++. Java и C ++ на самом деле являются совершенно разными языками, даже если их синтаксис похож, и способ их работы совершенно другой. На самом деле вам нужно будет отучиться от многих техник, которые популярны в Java, потому что они просто не будут работать так, как вы ожидаете в C ++. (Точно так же тот, кто изучает Java на основе знания C ++, столкнется с проблемами, потому что многие методы не работают).

0 голосов
/ 17 июня 2020

Вы пытаетесь вернуть объект абстрактного класса по значению. Проблема не связана с использованием шаблонов.

В C ++ для работы полиморфизма на основе наследования необходим указатель или ссылка.

Простым решением было бы заставить inverse возвращать std::unique_ptr<KinematicChainSegment<T>> и используйте std::make_unique в замещающих функциях.

Это решение может быть не идеальным, если вы часто используете объект не-полиморфным c способом - как показано в вашем примере - потому что это вызывает ненужное выделение и 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, но на сайте есть ответы на вопросы по этому поводу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...