
Part 2
Templates and variadic templates.
Abbiamo visto com'è strutturata (in partenza) un'interfaccia ed una runtime class in C++/WinRT,
ed a questo proposito ho mostrato un esempio di CRTP.
Prima di implementare le interfacce e le classi (WinRT style) nella nostra app è bene riassumere
brevemente, magari con degli esempi, qualche concetto C++, nello specifico:
Nota
In tutti gli esempi troverete come primo include il file Windows.h
per il semplice fatto che, dato che sto usando un compilatore Microsoft,
includendo quel file viene definito (tra le altre cose) il tipo interface.
Ciò non è strettamente necessario, infatti gli esempi sono scritti in C++ standard 17
e possono essere compilati con un qualunque compilatore C++ 17, sotto Windows,
Linux etc...
Pertanto se state usando un compilatore non Microsoft e la keyword interface non è definita,
aggiungete la seguente direttiva:
#include <basetyps.h>
Per iniziare, perchè mai dovrebbero essere usate queste tecniche? In quali casi? Qual'è lo scopo?
La risposta a queste domande è legata ad una delle pietre miliari del linguaggio C++ ( e di altri
linguaggi OOP): Inheritance (ereditarietà).
Come ben sappiamo, con l'inheritance, è possibile estendere le funzionalità degli oggetti, sovrascrivendo le esistenti (override)
od aggiungendone di nuove; in altre parole a partire da un un oggetto (classe) base, deriveremo uno o più
oggetti (classi); i nuovi oggetti a loro volta potranno essere classi base per altri ulteriori oggetti che derivano
da essi e così via.
Questo tipo di relazione tra oggetti è definita Is-a, ovvero una classe che discende
da un'altra è sia un oggetto del tipo classe derivata che base (e questo per tutta la catena).
Ora, sebbene l'ereditarietà sia utile in molti frangenti, spesso introduce dei grossi
problemi, in taluni casi la multiple inheritance innesca ambiguità che possono rendere impossibile la compilazione.
Un esempio tipico è il Dreaded Diamond Problem (o diamond problem) che introduce ambiguità quando
una classe eredita da altre due classi le quali a loro volta hanno una classe base in comune.
Per ovviare al problema, in tali casi, (oltre che rivedere seriamente la struttura dei nostri progetti), sarà necessario
usare il risolutore del campo d'azione ::, o ereditare le classi base come virtual.
//Diamond problem, sample and fix
#pragma once
#include <Windows.h>
#include <iostream>
class Base
{
public:
Base() = default;
virtual ~Base() = default;
void Print() { std::wcout << L"Print base" << std::endl; }
int GetVal() { return m_val; }
private:
int m_val = 100;
};
//Decomment virtual to fix the problem
class Derived_1 : /*virtual*/ public Base
{
public:
Derived_1() = default;
virtual ~Derived_1() = default;
void Print() { std::wcout << L"Print Derived_1" << std::endl; }
};
//Decomment virtual to fix the problem
class Derived_2 : /*virtual*/ public Base
{
public:
Derived_2() = default;
virtual ~Derived_2() = default;
void Print() { std::wcout << L"Print Derived_2" << std::endl; }
};
class Derived_3 : public Derived_1, public Derived_2
{
public:
Derived_3() = default;
virtual ~Derived_3() = default;
void Print() { std::wcout << L"Print Derived_3" << std::endl; }
};
//====================================
// Main
int Main()
{
//Ambiguous cast and access sample.
//Compilation fails if Base is not declared as virtual
//in Derived_1 and Derived_2
std::wcout << L" = = = = = = = = = = = = = = " << std::endl;
std::wcout << L"Ambiguous access sample:" << std::endl;
auto pDv = std::make_unique<Derived_3>();
Base* pBase = dynamic_cast<Base *>(pDv.get()); //<-- Not compile or warning
pBase->Print();
int val = pBase->GetVal();
Derived_1* pDv1 = dynamic_cast<Derived_1*>(pDv.get());
pDv1->Print();
val = pDv1->GetVal();
Derived_2* pDv2 = dynamic_cast<Derived_2*>(pDv.get());
pDv2->Print();
val = pDv2->GetVal();
pDv->Print();
val = pDv->GetVal(); //<-- Not compile
return 0;
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
//Compiler Output:
//warning C4540: dynamic_cast used to convert to inaccessible or ambiguous base; run-time test will fail ('Derived_3 *' to 'Base *')
//error C2385: ambiguous access of 'GetVal'
//message : could be the 'GetVal' in base 'Base'
Decommentando la keyword virtual nelle definizioni di Derived_1 e Derived_2
il problema sarà risolto, in quanto verrà implementata una sola copia di Base.
//Output: = = = = = = = = = = = = = = Ambiguous access sample: Print base Print Derived_1 Print Derived_2 Print Derived_3
Come abbiamo appena visto quindi la multiple inheritance è potenzialmente
pericolosa. Ma allora, per ridurre gli effetti collaterali dell'ereditarietà multipla,
come possiamo fare? Beh, abbiamo a disposizione varie tecniche:
Nota
Variadic template è una classe od una funzione template che accetta un arbitrario
numero di argomenti.
Abbiamo visto un'introduzione a variadic templates nella prima parte di questo tutorial a proposito
delle runtime classes WinRT implementate (per noi) da Visual Studio 2019.
Nell' esempio CRTP vs Mixin qui di seguito vedremo come alla fine, in output, il risultato finale
sarà identico sia seguendo il pattern CRTP che Mixin.
Teniamo presente che lo scopo, in entrambi i casi, è quello di estendere
una data classe aggiungendo delle nuove funzionalità, cercando di evitare le
trappole dell'ereditarietà multipla.
Doteremo di una funzione ReversePrint() una classe che già
possiede una sua funzione di stampa PrintData().
La classe Reverse in cui il metodo ReversePrint() è implementato è una classe template
che aderisce all'idioma CRTP nel namespace UseCRTP ed all'idioma Mixin nel namespace UseMixin.
A seguire vedremo più in dettaglio le differenze tra i due patterns.
CRTP vs Mixin sample
#pragma once
#include <Windows.h>
#include <iostream>
//CRTP
namespace UseCRTP
{
template <typename T>
class Reverse
{
public:
Reverse() = default;
virtual ~Reverse() = default;
void ReversePrint()
{
static_cast<T&>(*this).PrintData();
std::wstring fName = static_cast<T&>(*this).GetFirstName();
std::wstring lName = static_cast<T&>(*this).GetLastName();
std::wstring alias = static_cast<T&>(*this).GetAlias();
std::wcout << L"Reverse Print (CRTP):"
<< std::endl
<< alias << L" :"
<< lName << L", "
<< fName
<< std::endl << std::endl;
}
};
class SuperHero :public Reverse<SuperHero>
{
public:
SuperHero() = default;
virtual ~SuperHero() = default;
SuperHero(std::wstring fName, std::wstring lName, std::wstring alias)
{
m_FirstName = fName;
m_LastName = lName;
m_Alias = alias;
}
void PrintData() { std::wcout <<L"Regular Print:" << std::endl
<< m_FirstName << L", "
<< m_LastName << L" :"
<< m_Alias << std::endl
<< std::endl; }
std::wstring GetFirstName() { return m_FirstName;}
std::wstring GetLastName() { return m_LastName;}
std::wstring GetAlias() { return m_Alias;}
void SetFirstName(std::wstring const& fName) { m_FirstName = fName; }
void SetLastName(std::wstring const& lName) { m_LastName = lName; }
void SetAlias(std::wstring const& alias) { m_Alias = alias; }
private:
std::wstring m_FirstName = L"";
std::wstring m_LastName = L"";
std::wstring m_Alias = L"";
};
}
//========================================
namespace UseMixin
{
class SuperHero
{
public:
SuperHero() = default;
virtual ~SuperHero() = default;
SuperHero(std::wstring fName, std::wstring lName, std::wstring alias)
{
m_FirstName = fName;
m_LastName = lName;
m_Alias = alias;
}
void PrintData() { std::wcout <<L"Regular Print:" << std::endl
<< m_FirstName << L", "
<< m_LastName << L" :"
<< m_Alias << std::endl
<< std::endl; }
std::wstring GetFirstName() { return m_FirstName;}
std::wstring GetLastName() { return m_LastName;}
std::wstring GetAlias() { return m_Alias;}
void SetFirstName(std::wstring const& fName) { m_FirstName = fName; }
void SetLastName(std::wstring const& lName) { m_LastName = lName; }
void SetAlias(std::wstring const& alias) { m_Alias = alias; }
private:
std::wstring m_FirstName = L"";
std::wstring m_LastName = L"";
std::wstring m_Alias = L"";
};
}
template<typename T>
class Reverse :public T
{
public:
Reverse() = default;
virtual ~Reverse() = default;
Reverse<T>(std::wstring fName, std::wstring lName, std::wstring alias )
{
T::SetFirstName(fName);
T::SetLastName(lName);
T::SetAlias(alias);
}
void ReversePrint()
{
T::PrintData();
std::wcout << L"Reverse Print (Mixin):"
<< std::endl
<< T::GetAlias() << L" :"
<< T::GetLastName() << L", "
<< T::GetFirstName()
<< std::endl << std::endl;
}
};
}
//========================================
//Main
int Main()
{
//CRTP
{
using namespace UseCRTP;
//We can use smart pointers
auto pshCRTP = std::make_unique<SuperHero>(L"Oliver",L"Queen",L"Green Arrow");
pshCRTP->ReversePrint();
//Or plain old
SuperHero shCRTP {L"Barry",L"Allen",L"The Flash"};
shCRTP.ReversePrint();
}
//Mixin
{
using namespace UseMixin;
//We can use smart pointers
auto pshMixin = std::make_unique<Reverse<SuperHero>>(L"Tony",L"Stark",L"Iron Man");
pshMixin->ReversePrint();
//Or plain old
using CMixin = Reverse<SuperHero>
CMixin shMixin {L"Miles",L"Morales",L"Ultimate Spider-Man"};
shMixin.ReversePrint();
}
return 0;
}
//Output: Regular Print: Oliver, Queen :Green Arrow Reverse Print (CRTP): Green Arrow :Queen, Oliver Regular Print: Barry, Allen :The Flash Reverse Print (CRTP): The Flash :Allen, Barry Regular Print: Tony, Stark :Iron Man Reverse Print (Mixin): Iron Man :Stark, Tony Regular Print: Miles, Morales :Ultimate Spider-Man Reverse Print (Mixin): Ultimate Spider-Man :Morales, Miles
Principali differenze tra CRTP e Mixin nell'esempio
All together now.
Concludiamo il breve riassunto su alcune funzionalità del C++
con un esempio sulle variadic template classes e functions.
Di seguito vedremo come, utilizzando un misto di CRTP, Mixin e variadic template
è possibile estendere delle classi a nostro piacimento in maniera alternativa
rispetto alla "classica" inheritance.
I più attenti avranno a questo punto già colto il controsenso intrinseco:
per limitare i problemi dell'inheritance useremo... l'inheritance (anche se in modo differente).
Variadic template class and function sample.
Ho in precedenza già introdotto variadic templates; come detto
si tratta di classi o funzioni templates che supportano un qualunque
numero di argomenti.
Questa caratteristica introduce una grande flessibilità nello sviluppare
applicazioni anche molto complesse.
App di esempio: prima versione.
(il cliente ha sempre ragione...forse)
Supponiamo di essere stati incaricati da un nostro committente di creare un'applicazione
che manda delle emails ai suoi clienti.
Le funzionalità richieste in sede di progetto sono:
#pragma once
#include <Windows.h>
#include <iostream>
#include <vector>
namespace Messages_vers_01
{
interface IMessages
{
struct __tagMESSAGES
{
std::wstring to;
std::wstring subject;
std::wstring text;
};
using Message = __tagMESSAGES;
virtual void Compose(const Message& msg) = 0;
virtual void Send() = 0;
virtual std::vector<Message> Read() = 0;
};
using Messages = IMessages::Message;
interface IDisplay
{
virtual void Display() = 0;
};
class CEmail :public IMessages, public IDisplay
{
public:
CEmail() = default;
virtual ~CEmail() = default;
//IMessages
virtual void Compose(const Message& msg) { m_email.push_back(msg); };
virtual void Send() { std::wcout << std::endl << L" Email sent! " << std::endl; };
virtual std::vector<Message> Read() { return m_email; };
//IDisplay
void Display()
{
std::vector<Messages>emails_sent = Read();
std::wcout << std::endl;
std::wcout << L" * * * * * * * * * * *" << std::endl;
for (size_t i = 0; i < emails_sent.size(); i++)
{
std::wcout << L"\n\r";
std::wcout << L" # " << (i + 1) << std::endl;
std::wcout << L" To: \t" << emails_sent[i].to.c_str() << std::endl;
std::wcout << L" Subject:\t" << emails_sent[i].subject.c_str() << std::endl;
std::wcout << L" Text: \t" << emails_sent[i].text.c_str() << std::endl;
std::wcout << L"\n\r";
}
std::wcout << L" * * * * * * * * * * *" << std::endl;
}
private:
std::vector<Message> m_email;
};
}
//=====================================
//Main
int main()
{
{
using namespace Messages_vers_01;
Messages email_msg;
CEmail em;
//#1
email_msg.to = L"my_best_client@myclient.com";
email_msg.subject = L"Hey, how you doing?";
email_msg.text = L"Hello my best client! Greetings.";
em.Compose(email_msg);
em.Send();
//#2
email_msg.to = L"next_client@myclient.com";
email_msg.subject = L"A word to my NEXT best client!";
email_msg.text = L"Hello my next best client! Do you know we are in your zone?.";
em.Compose(email_msg);
em.Send();
em.Display();
return 0;
}
//Output Email sent! Email sent! * * * * * * * * * * * # 1 To: my_best_client@myclient.com Subject: Hey, how you doing? Text: Hello my best client! Greetings. # 2 To: next_client@myclient.com Subject: A word to my NEXT best client! Text: Hello my next best client! Do you know we are in your zone? * * * * * * * * * * *
Niente di spettacolare, la classe CEmail implementa le interfacce IMessagese ed IDisplay.
Nota
Nelle interfacce IMessages e IDisplay sono dichiarate delle funzioni virtuali pure;
per chi non ricordasse, una funzione virtuale pura DEVE essere obbligatoriamente
implementata nelle classi che derivano da classi astratte e/o che implementano
le interfacce in cui le funzioni virtuali pure son dichiarate.
Il committente è contento e dopo qualche tempo ci incarica di aggiungere all'applicazione
la possibilità di mandare, oltre le emails, dei messaggi SMS.
Noi eseguiamo ed implementiamo la nuova funzionalità... nel modo errato.
Nota
L'applicazione sicuramente funziona, ma ci sono errori di progettazione.
Qui sotto il codice della nostra applicazione (seconda versione).
#pragma once
#include <Windows.h>
#include <iostream>
#include <vector>
namespace Messages_vers_02
{
interface IMessages
{
struct __tagMESSAGES
{
std::wstring to;
std::wstring subject;
std::wstring text;
};
using Message = __tagMESSAGES;
virtual void Compose(const Message& msg) = 0;
virtual void Send() = 0;
virtual std::vector<Message> Read() = 0;
};
using Messages = IMessages::Message;
interface IDisplay
{
virtual void Display() = 0;
};
class CSMS :public IMessages, public IDisplay
{
public:
CSMS() = default;
virtual ~CSMS() = default;
//IMessages
virtual void Compose(const Message& msg) { m_sms.push_back(msg); };
virtual void Send() { std::wcout << std::endl << L" SMS sent! " << std::endl; };
virtual std::vector<Message> Read() { return m_sms; };
//IDisplay
void Display()
{
std::wcout << std::endl;
std::wcout << L" * * * * * * * * * * *" << std::endl;
for (size_t i = 0; i < m_sms.size(); i++)
{
std::wcout << L"\n\r";
std::wcout << L" # " << (i + 1) << std::endl;
std::wcout << L" To: \t" << m_sms[i].to.c_str() << std::endl;
std::wcout << L" Subject:\t" << m_sms[i].subject.c_str() << std::endl;
std::wcout << L" Text: \t" << m_sms[i].text.c_str() << std::endl;
std::wcout << L"\n\r";
}
std::wcout << L" * * * * * * * * * * *" << std::endl;
}
private:
std::vector<Message> m_sms;
};
class CEmail :public IMessages, IDisplay
{
public:
CEmail() = default;
virtual ~CEmail() = default;
//IMessages
virtual void Compose(const Message& msg) { m_email.push_back(msg); };
virtual void Send() { std::wcout << std::endl << L" Email sent! " << std::endl; };
virtual std::vector<Message> Read() { return m_email; };
//IDisplay
void Display()
{
std::wcout << std::endl;
std::wcout << L" * * * * * * * * * * *" << std::endl;
for (size_t i = 0; i < m_email.size(); i++)
{
std::wcout << L"\n\r";
std::wcout << L" # " << (i + 1) << std::endl;
std::wcout << L" To: \t" << m_email[i].to.c_str() << std::endl;
std::wcout << L" Subject:\t" << m_email[i].subject.c_str() << std::endl;
std::wcout << L" Text: \t" << m_email[i].text.c_str() << std::endl;
std::wcout << L"\n\r";
}
std::wcout << L" * * * * * * * * * * *" << std::endl;
}
private:
std::vector<Message> m_email;
};
}
//Main
int main()
{
{
using namespace Messages_vers_02;
//Emails section
Messages email_msg;
CEmail em;
//#1
email_msg.to = L"my_best_client@myclient.com";
email_msg.subject = L"Hey, how you doing?";
email_msg.text = L"Hello my best client! Greetings.";
em.Compose(email_msg);
em.Send();
//#2
email_msg.to = L"next_client@myclient.com";
email_msg.subject = L"A word to my NEXT best client!";
email_msg.text = L"Hello my next best client! Do you know we are in your zone?";
em.Compose(email_msg);
em.Send();
em.Display();
//=============================================
//SMSs section
Messages sms_msg;
CSMS sms;
//#1
sms_msg.to = L"My wife (+39)123.123.123";
sms_msg.text = L"I'm still at work. See you later.";
sms.Compose(sms_msg);
sms.Send();
//#2
sms_msg.to = L"My new girlfriend (+39)345.345.345";
sms_msg.text = L"Hi, I'm coming to you right now.";
sms.Compose(sms_msg);
sms.Send();
sms.Display();
}
return 0;
}
//Output Email sent! Email sent! * * * * * * * * * * * # 1 To: my_best_client@myclient.com Subject: Hey, how you doing? Text: Hello my best client! Greetings. # 2 To: next_client@myclient.com Subject: A word to my NEXT best client! Text: Hello my next best client! Do you know we are in your zone? * * * * * * * * * * * SMS sent! SMS sent! * * * * * * * * * * * # 1 To: My wife (+39)123.123.123 Text: I'm still at work. See you later. # 2 To: My new girlfriend (+39)345.345.345 Text: Hi, I'm coming to you right now. * * * * * * * * * * *
Come vediamo nello schema, implementiamo l'interfaccia IMessages solo nella classe template
(base) MessagesT; quest'ultima è una classe template che oltre ad implementare IMessages eredita
la classe del suo argomento (T).
L'argomento (T) di essa (da progetto) è la classe variadic template MessageHelper. Come possiamo
vedere nel codice (più sotto), la classe MessageHelper eredita la classe del suo argomento template (D).
Possiamo anche vedere la sintassi per definire una classe variadic template (typename...).
La classe MessageHelper si occuperà di fornire le funzioni comuni a tutti i tipi di messaggi:
Compose(), Send() e Read().
Tuttavia, come detto prima, se la funzione Send della classe generica MessageHelper può essere
invocata quando è necessaria un'ipotetica funzione Send comune a più tipi
di messaggi, avremo sicuramente anche la necessità di chiamare la versione specializzata di Send
specifica per ogni classe di messaggio.
Questo scopo lo otteniamo aggiungendo alla classe MessageHelper una funzione variadic template
il cui nome è SendSpecialized.
Possiamo notare la sintassi della variadic template function SendSpecialized:
template <typename I, typename... Args> void SendSpecialized(I i, Args... args) { I::Send(args...); } //...... //......Stiamo semplicemente dichiarando una funzione template con più argomenti di cui il primo è I e l'altro (Args...) (o per meglio dire gli altri) sono arbitrari.
Di seguito il codice della nostra applicazione (terza versione).
#pragma once
#include <Windows.h>
#include <iostream>
#include <vector>
namespace Messages_vers_03
{
interface IMessages
{
struct __tagMESSAGES
{
std::wstring to;
std::wstring subject;
std::wstring text;
};
using Message = IMessages::__tagMESSAGES;
virtual void Compose(const Message& msg) = 0;
virtual void Send() = 0;
virtual std::vector<Message< Read() = 0;
};
using Message = IMessages::Message;
//Mixin template class
template <typename T>
class MessagesT : public IMessages, public T
{
public:
//default ctor/dtor
MessagesT() = default;
virtual ~MessagesT() = default;
//IMessages
virtual void Compose(const Message& msg) override { T::Compose(msg); }
virtual void Send() override { T::Send(); }
virtual std::vector<Message> Read() override { return T::Read(); }
};
//Mixin variadic template class
template<typename... D>
class MessagesHelper :public D...
{
public:
MessagesHelper() = default;
virtual ~MessagesHelper() = default;
//IMessages
void Compose(const Message& msg) { m_msg.push_back(msg); }
void Send() { std::wcout << std::endl; std::wcout << L"--> Generalized send method executed..." << std::endl; }
std::vector<Message> Read() { return m_msg; }
//variadic template function
template <typename I, typename... Args>
void SendSpecialized(I i, Args... args)
{
I::Send(args...);
}
private:
std::vector<Message> m_msg;
};
interface IDisplay
{
virtual void Display() = 0;
virtual void Display(const std::vector<Message>& msg) = 0;
};
//Mixin variadic template class
template <typename... J>
class Displayable : public IDisplay, public J...
{
public:
Displayable() = default;
virtual ~Displayable() = default;
virtual void Display() override { std::wcout << std::endl; std::wcout << L"--> Generalized Display method executed" << std::endl; }
virtual void Display(const std::vector<Message>& msg) override { std::wcout << std::endl; std::wcout << L"--> Generalized Display method executed" << std::endl; }
//variadic template function
template <typename V,typename...Args>
void Display(V v, Args... args)
{
std::wcout << std::endl;
std::wcout << L" * * * * * * * * * * *" << std::endl;
V::Display(args...);
std::wcout << L" * * * * * * * * * * *" << std::endl;
std::wcout << std::endl;
}
};
class CEmail
{
public:
//default ctor/dtor
CEmail() = default;
virtual ~CEmail() = default;
void Send() { std::wcout << std::endl; std::wcout << L"--> Specialized email send method executed..." << std::endl; }
void Display(const std::vector<Message>& msg)
{
std::wcout << std::endl;
std::wcout << L"--> Specialized email display method executed..." << std::endl;
for (size_t i = 0; i < msg.size(); i++)
{
std::wcout << L"\n\r";
std::wcout << L" # " << (i + 1) << std::endl;
std::wcout << L" To: \t" << msg[i].to.c_str() << std::endl;
std::wcout << L" Subject: \t" << msg[i].subject.c_str() << std::endl;
std::wcout << L" Text: \t" << msg[i].text.c_str() << std::endl;
std::wcout << L"\n\r";
}
}
};
class CSMS
{
public:
//default ctor/dtor
CSMS() = default;
virtual ~CSMS() = default;
void Send() { std::wcout << std::endl; std::wcout << L"--> Specialized sms send method executed..." << std::endl; }
void Display(const std::vector<Message>& msg)
{
std::wcout << std::endl;
std::wcout << L"--> Specialized sms display method executed..." << std::endl;
for (size_t i = 0; i < msg.size(); i++)
{
std::wcout << L"\n\r";
std::wcout << L" # " << (i + 1) << std::endl;
std::wcout << L" To: \t" << msg[i].to.c_str() << std::endl;
std::wcout << L" Text: \t" << msg[i].text.c_str() << std::endl;
std::wcout << L"\n\r";
}
}
};
//New message types declaration
using TextMessage = MessagesT<MessagesHelper<Displayable<CSMS>>>;
using EmailMessage = MessagesT<MessagesHelper<Displayable<CEmail>>>;
}
//Main
int main()
{
{
using namespace Messages_vers_03;
Message msg;
TextMessage smsType;
EmailMessage emType;
//Email message type
//# 1 email type message
msg.to = L"my_best_client@myclient.com";
msg.subject = L"Hey, how you doing?";
msg.text = L"Hello my best client! Greetings.";
emType.Compose(msg);
//# 2 email type message
msg.to = L"next_client@myclient.com";
msg.subject = L"A word to my NEXT best client!";
msg.text = L"Hello my next best client! Do you know we are in your zone?";
emType.Compose(msg);
//performs generic send method, optional to specialized method,
//it is not necessary here
emType.Send();
emType.SendSpecialized<CEmail>(emType);
//performs generic display method, optional to specialized method,
//it is not necessary here
emType.Display();
emType.Display<CEmail>(emType, emType.Read());
//==========================================
//Text message type
//# 1 text type message
msg.to = L"My wife (+39)123.123.123";
msg.text = L"I'm still at work. See you later.";
smsType.Compose(msg);
//# 2 text type message
msg.to = L"My new girlfriend (+39)345.345.345";
msg.text = L"Hi, I'm coming to you right now.";
smsType.Compose(msg);
//performs generic send method, optional to specialized method,
//it is not necessary here
smsType.Send();
smsType.SendSpecialized<CSMS>(smsType);
//performs generic display method, optional to specialized method,
//it is not necessary here
smsType.Display();
smsType.Display<CSMS>(smsType, smsType.Read());
}
return 0;
}
//Output: --> Generalized send method executed... --> Specialized email send method executed... --> Generalized Display method executed * * * * * * * * * * * --> Specialized email display method executed... # 1 To: my_best_client@myclient.com Subject: Hey, how you doing? Text: Hello my best client! Greetings. # 2 To: next_client@myclient.com Subject: A word to my NEXT best client! Text: Hello my next best client! Do you know we are in your zone? * * * * * * * * * * * --> Generalized send method executed... --> Specialized sms send method executed... --> Generalized Display method executed * * * * * * * * * * * --> Specialized sms display method executed... # 1 To: My wife (+39)123.123.123 Text: I'm still at work. See you later. # 2 To: My new girlfriend (+39)345.345.345 Text: Hi, I'm coming to you right now. * * * * * * * * * * *
Possiamo vedere come in questa nuova versione
dell'app abbiamo, da una parte, evitato il problema della multiple inheritance, pur
mantenendola in altra forma e come alcune funzioni siano comunque polimorfiche.
Inoltre l'introduzione nel progetto dei variadic templates rende più semplice l'enstensione
ad eventuali nuovi tipi di messaggi nel tempo.
In effetti, prima abbiamo detto che era necessario riscrivere l'app (di fantasia) proprio perchè ci era stato chiesto
di aggiungere altri tipi di messaggi (vocali e immagini).
Stavolta però aggiungere le nuove funzionalità è un lavoro di 5 minuti.
Aggiungiamo altre due classi al progetto:
//.........
//.........
class CVocal
{
public:
//default ctor/dtor
CVocal() = default;
virtual ~CVocal() = default;
void Send() { std::wcout << std::endl; std::wcout << L"--> Specialized vocal send method executed..." << std::endl; }
void Display(const std::vector<Message>& msg)
{
std::wcout << std::endl;
std::wcout << L"--> Specialized vocal display method executed..." << std::endl;
for (size_t i = 0; i < msg.size(); i++)
{
std::wcout << L"\n\r";
std::wcout << L" # " << (i + 1) << std::endl;
std::wcout << L" To: \t" << msg[i].to.c_str() << std::endl;
std::wcout << L" Audio: \t" < msg[i].audio_video_image.c_str() << std::endl;
std::wcout << L"\n\r";
}
}
};
class CVideoOrImage
{
public:
//default ctor/dtor
CVideoOrImage() = default;
virtual ~CVideoOrImage() = default;
void Send() { std::wcout << std::endl; std::wcout << L"--> Specialized image or video send method executed..." << std::endl; }
void Display(const std::vector<Message>& msg)
{
std::wcout << std::endl;
std::wcout << L"--> Specialized image or video display method executed..." << std::endl;
for (size_t i = 0; i < msg.size(); i++)
{
std::wcout << L"\n\r";
std::wcout <<L" # " << (i + 1) << std::endl;
std::wcout << L" To: \t" << msg[i].to.c_str() << std::endl;
std::wcout << L" Image: \t" << msg[i].audio_video_image.c_str() << std::endl;
std::wcout << L"\n\r";
}
}
};
//.....
//.....
using VocalMessage = MessagesT<MessagesHelper<Displayable<CVocal>>>;
using VideoOrImage = MessagesT<MessagesHelper<Displayable<CVideoOrImage>>>;
//.....
//.....
//Main
int main()
//.....
//.....
//Vocal Messages
VocalMessage vmType;
msg.to = L"(+39)123.123.123";
msg.audio_video_image = L"vocal.mp4";
vmType.Compose(msg);
vmType.SendSpecialized<CVocal>(vmType);
vmType.Display<CVocal>(vmType, vmType.Read());
//Image Messages
VideoOrImage vd_imType;
msg.to = L"(+39)123.123.123";
msg.audio_video_image = L"image.png";
vd_imType.Compose(msg);
vd_imType.SendSpecialized<CVideoOrImage>(vd_imType);
vd_imType.Display<CVideoOrImage>(vd_imType, vd_imType.Read());
//.....
//.....
//Output: --> Specialized vocal send method executed... * * * * * * * * * * * --> Specialized vocal display method executed... # 1 To: (+39)123.123.123 Audio: vocal.mp4 * * * * * * * * * * * --> Specialized image or video send method executed... * * * * * * * * * * * --> Specialized image or video display method executed... # 1 To: (+39)123.123.123 Image: image.png * * * * * * * * * * *
Giuseppe Pischedda 2020
Se il post ti è utile puoi fare una donazione all'autore, l'importo è a tua libera scelta.
Grazie