Pagine

sabato 24 novembre 2012

C++ -> Template Friendship and Forward Decls

Come tutti i newbie del C++ si arriva prima o poi a chiedersi se sia possibile dichiarare la friendship e la forward declaration di un template.  A quanto pare lo standard dice di si e da una veloce ricerca in rete la funzionalità risulta ampiamente supportata (sicuramente sia dal MSVC sia dal GCC).

Per la friendship si ha che:
friend class Foo;
diventa, nel caso in cui Foo sia un template:
template<typename T> friend class Foo;

Nel caso delle forward declaration si piazza nell'header la dichiarazione del template da forward-declarare con la medesima sintassi che si usa normalmente per le classi templatiche.
Nell'esempio che segue viene utilizzata sia la friendship che la forward declaration "templatica": l'esempio in se non è assolutamente da prendere come modello di buona programmazione e ha il solo fine di mostrare la sintassi.
Si ha:
  • Un template Foo, con un metodo Print() che consente di stampare il valore intero di un membro privato contenuto in un oggetto di tipo Foo2. Giusto per dare un senso all'uso del template, il metodo Print stampa anche il valore di un builtin il cui tipo è dipendente dal parametro templatico.  L'oggetto di tipo Foo2 è il builtin vengono passati a Foo nel costruttore.
  • Una classe Foo2 che contiene un metodo PrintFoo(): questo prende in ingresso un oggetto di tipo Foo e ne invoca il metodo Print().
Un classico problema di dipendenza circolare risolvibile con forward declaration.
Innanzitutto dichiaro in un header la classe Foo:
//foo.h
#pragma once

#include<iostream>
#include "foo2.h"


namespace FooNamespace
{
      using namespace Foo2Namespace;

      template <typename T>
      class Foo
      {
      public:
           Foo(T toPrintObject, Foo2 foo2Object) 
               : mToPrintObject(toPrintObject)
               , mFoo2Object(foo2Object)
           {
                //Nothing to do here
           }

           void Print()
           {
                std::cout << "Foo2 toPrintObject value = " 
                          << mToPrintObject << std::endl;
                std::cout << "Foo  toPrint       value = " 
                          << mFoo2Object.mToPrintValue 
                          << std::endl;
           }

     private:
          typename T mToPrintObject;
          Foo2    mFoo2Object; 
     };
}

//end header

Nell'header ho incluso l'header di Foo2. Notare che all'interno della classe Foo ho 2 membri, uno dipendente dal parametro templatico e uno di tipo Foo2. Quest'ultimo posso istanziarlo tranquillamente poichè, avendone incluso l'header, risulta un tipo completo di cui è conosciuto il memory layout.

L'header Foo2.h è invece definito come segue:
//foo2.h
#pragma once

//forward declaration
namespace FooNamespace
{
      template <typename T> class Foo;
}

namespace Foo2Namespace
{
     class Foo2
     {
     public:
          Foo2(int toPrintValue)
               : mTtoPrintValue(toPrintValue)
          {
               //Nothing to do here
          }
  
          template <typename T>
          void PrintFoo(Foo<T>& fooObject)
          {
               fooObject.Print();
          }

          template <typename T>
          void PrintFoo(Foo<T>* fooObject)
          {
               fooObject->Print();
          }

      private:
          //friendship template declaration
          template<typename T> friend class FooNamespace::Foo;
          int mToPrintValue;
      };
}

//end header

In questo caso l'header di Foo non è stato incluso proprio al fine di evitare la dipendenza circolare. Ma poiché le funzioni e la friendship sono dipendenti da Foo, devo segnalare che Foo esiste. Per farlo è sufficiente dichiarare Foo prima che venga utilizzata da Foo2.

Dato che Foo esiste in un altro namespace (FooNamespace) occorre dichiararla all'interno del namespace stesso:
//forward declaration
namespace FooNamespace
{
      template <typename T> class Foo;
}

Notare che nei 2 overload delle funzioni PrintFoo() si prende in ingresso una volta un puntatore e una volta un reference. Quando in una classe si usa un tipo che è stato forward declarato è possibile utilizzare solo puntatori e reference. Se avessi passato Foo<T> per copia il compilatore avrebbe segnalato un errore di tipo "incomplete type not allowed".

Il main è definito in questo modo:
#include "Foo.h"

using namespace Foo2Namespace;
using namespace FooNamespace;

int main(int argc, char* argv[])
{
     Foo2 foo2(100);

     Foo<int> intFoo(10, foo2);
     Foo<float> floatFoo(10.5, foo2);

     std::cout << "----- intFoo print ----- \n\n"; 
     foo2.PrintFoo(intFoo);
     std::cout << "\n----- floatFoo print -----\n\n"; 
     foo2.PrintFoo(&floatFoo);

     getchar();
     return 0;
}

L'output è il seguente:

Nessun commento:

Posta un commento