Свободные интерфейсы и наследование в C++


Я хотел бы построить базовый (абстрактный) класс (назовем его type::base) с некоторой общей функциональностью и плавным интерфейсом, проблема, с которой я сталкиваюсь, - это возвращаемый тип всех этих методов

  class base {
    public:
       base();
       virtual ~base();

       base& with_foo();
       base& with_bar();
    protected:
       // whatever...
  };

Теперь я мог бы сделать подтипы, например:

  class my_type : public base {
    public:
      myType();        
      // more methods...
  };

Проблема возникает при использовании таких подтипов, как:

 my_type build_my_type()
 {
    return my_type().with_foo().with_bar();
 }

Это не будет компилироваться, потому что мы возвращаем base вместо my_type.

Я знаю, что мог бы просто:

 my_type build_my_type()
 {
    my_type ret;
    ret.with_foo().with_bar();

    return ret;
 }

Но я думал, как я могу это реализовать, и я не нашел никаких обоснованных идей, каких-то предложений?

5 7

5 ответов:

Эту проблему "потери типа" можно решить с помощью шаблонов-но это довольно сложно.

Напр.

class Pizza
{
  string topping;
public:
  virtual double price() const;
};

template <class T, class Base>
class FluentPizza : public Base
{
  T* withAnchovies() { ... some implementation ... };
};

class RectPizza : public FluentPizza<RectPizza, Pizza>
{
  double price() const { return length*width; :) }
};

class SquarePizza : public FluentPizza<SquarePizza, RectPizza>
{
   ... something else ...
};

Затем вы можете написать

SquarePizza* p=(new SquarePizza)->withAnchovies();

Картина такова, что вместо

class T : public B

Вы пишете

class T : public Fluent<T, B>

Другой подход может заключаться не в использовании интерфейса fluent на объектах, а на указателях:

class Pizza { ... };
class RectPizza { ... };
class SquarePizza { ... whatever you might imagine ... };

template <class T>
class FluentPizzaPtr
{
  T* pizza;
public:
  FluentPizzaPtr withAnchovies() {
    pizza->addAnchovies(); // a nonfluent method
    return *this;
  }
};

Используйте вот так:

FluentPizzaPtr<SquarePizza> squarePizzaFactory() { ... }

FluentPizzaPtr<SquarePizza> myPizza=squarePizzaFactory().withAnchovies();

Вы должны возвращать ссылки / указатели, и вам не нужно хранить информацию о типе.

class base {
  public:
     base();
     virtual ~base();

     base &with_foo();
     base &with_bar();
  protected:
     // whatever...
};

class my_type : public base {
  public:
    my_type();        
    // more methods...
};

base *build_my_type()
{
   return &new my_type()->with_foo().with_bar();
}

У вас уже есть виртуальный деструктор. Вероятно, у вас есть и другие виртуальные функции. Доступ ко всему через базовый тип и виртуальные функции, объявленные там.

Одно решение будет работать следующим образом:

return *static_cast<my_type*>(&my_type().with_foo().with_bar());

Использование static_cast в основном говорит компилятору: "я знаю, что я здесь делаю".

В языке C++ вы должны ретурировать указатели или ссылки, а не значения. Кроме того, вы можете объяснить, что вы подразумеваете под "плавными интерфейсами".

Способ, которым я сделал бы это в C#, и я думаю, что он будет работать и в C++, - это предоставить реализацию по умолчанию для with_foo() и with_bar()... Простите мой c#, но:

class base {
  virtual base with_foo()
  { throw new NotImplementedException(); }
  virtual base with_bar();
  { throw new NotImplementedException(); }
}