Sök…


Introduktion

Ett mönster där en klass ärver från en klassmall med sig själv som en av dess mallparametrar. CRTP används vanligtvis för att tillhandahålla statisk polymorfism i C ++.

Det konstigt återkommande mallmönstret (CRTP)

CRTP är ett kraftfullt, statiskt alternativ till virtuella funktioner och traditionell arv som kan användas för att ge typer egenskaper vid sammanställningstid. Det fungerar genom att ha en basklassmall som tar den härledda klassen som en av dess mallparametrar. Detta tillåter det att lagligt utföra en static_cast av dess this pekare till härledda klassen.

Naturligtvis innebär detta också att en CRTP-klass alltid måste användas som basklass för någon annan klass. Och den härledda klassen måste skicka sig själv till basklassen.

C ++ 14

Låt oss säga att du har en uppsättning containrar som alla stöder funktionerna begin() och end() . Standardbibliotekets krav på containrar kräver mer funktionalitet. Vi kan designa en CRTP-basklass som tillhandahåller den funktionen, baserad endast på begin() och end() :

#include <iterator>
template <typename Sub>
class Container {
  private:
    // self() yields a reference to the derived type
    Sub& self() { return *static_cast<Sub*>(this); }
    Sub const& self() const { return *static_cast<Sub const*>(this); }

  public:
    decltype(auto) front() {
      return *self().begin();
    }

    decltype(auto) back() {
      return *std::prev(self().end());
    }

    decltype(auto) size() const {
      return std::distance(self().begin(), self().end());
    }

    decltype(auto) operator[](std::size_t i) {
      return *std::next(self().begin(), i);
    }
};

Ovanstående klass tillhandahåller funktionerna front() , back() , size() och operator[] för alla underklasser som ger begin() och end() . Ett exempel på underklass är en enkel dynamiskt tilldelad matris:

#include <memory>
// A dynamically allocated array
template <typename T>
class DynArray : public Container<DynArray<T>> {
  public:
    using Base = Container<DynArray<T>>;

    DynArray(std::size_t size)
      : size_{size},
      data_{std::make_unique<T[]>(size_)}
    { }

    T* begin() { return data_.get(); }
    const T* begin() const { return data_.get(); }
    T* end() { return data_.get() + size_; }
    const T* end() const { return data_.get() + size_; }

  private:
    std::size_t size_;
    std::unique_ptr<T[]> data_;
};

Användare av klassen DynArray kan DynArray använda gränssnitten som tillhandahålls av CRTP-basklassen på följande sätt:

DynArray<int> arr(10);
arr.front() = 2;
arr[2] = 5;
assert(arr.size() == 10);

Användbarhet: Detta mönster undviker särskilt virtuella funktionssamtal vid körning som inträffar genom att gå ner i arvshierarkin och helt enkelt förlitar sig på statiska avkastningar:

DynArray<int> arr(10);
DynArray<int>::Base & base = arr;
base.begin(); // no virtual calls

Den enda statiska roll som finns inuti funktionen begin() i basklassen Container<DynArray<int>> gör att kompilatorn drastiskt kan optimera koden och ingen virtuell tabelluppsökning händer vid körning.

Begränsningar: Eftersom basklassen är mallad och olika för två olika DynArray s är det inte möjligt att lagra pekare till sina basklasser i en typ-homogen grupp som man vanligtvis kan göra med normal arv där basklassen inte är beroende av den härledda typ:

class A {};
class B: public A{};

A* a = new B;

CRTP för att undvika kopiering av kod

Exemplet i Besöksmönster ger ett övertygande användningsfall för CRTP:

struct IShape
{
    virtual ~IShape() = default;

    virtual void accept(IShapeVisitor&) const = 0;
};

struct Circle : IShape
{
    // ...        
    // Each shape has to implement this method the same way
    void accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }
    // ...
};

struct Square : IShape
{
    // ...    
    // Each shape has to implement this method the same way
    void accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }
    // ...
};

Varje IShape av IShape måste implementera samma funktion på samma sätt. Det är mycket extra maskinskrivning. Istället kan vi introducera en ny typ i hierarkin som gör detta för oss:

template <class Derived>
struct IShapeAcceptor : IShape {
    void accept(IShapeVisitor& visitor) const override {
        // visit with our exact type
        visitor.visit(*static_cast<Derived const*>(this));
    }
};

Och nu måste varje form helt enkelt ärva från acceptorn:

struct Circle : IShapeAcceptor<Circle>
{
    Circle(const Point& center, double radius) : center(center), radius(radius) {}
    Point center;
    double radius;
};

struct Square : IShapeAcceptor<Square>
{
    Square(const Point& topLeft, double sideLength) : topLeft(topLeft), sideLength(sideLength) {}    
    Point topLeft;
    double sideLength;
};

Ingen kopia krävs.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow