65.9K
CodeProject 正在变化。 阅读更多。
Home

C++/WinRT 运行时组件在 Win32 应用程序中 - 第二部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2022 年 1 月 14 日

CPOL

11分钟阅读

viewsIcon

6941

downloadIcon

101

第二部分 - 模板和可变参数模板

引言

  • 可变参数模板类/函数
  • CRTP
  • Mixin

注意:在所有示例中,您都会发现 `Windows.h` 文件是第一个包含的头文件,原因很简单,因为我使用的是 Microsoft 编译器,包含该文件(除其他外)定义了 `interface` 类型。

这并非绝对必要,实际上,示例是用标准的 C++ 17 编写的,并且可以在 Windows、Linux 等任何 C++ 17 编译器下编译。

因此,如果您使用的是非 Microsoft 编译器且 `interface` 关键字未定义,请添加以下指令

#include <basetyps.h>

首先,为什么要使用这些技术?在哪些情况下使用?目的是什么?这些问题的答案与 C++(和其他面向对象语言)的基石之一:继承有关

众所周知,通过继承,可以扩展对象的 functionality,覆盖现有 functionality(override)或添加新的 functionality;换句话说,从一个基本对象(类)开始,我们将派生出一到多个对象(类);新对象反过来可以成为其他派生自它们的附加对象的基类,依此类推。

这种对象之间的关系称为“是”(Is-a),即一个类继承自另一个类,它既是派生类对象的类型,也是基类对象的类型(整个链条都是如此)。现在,尽管继承在许多情况下都很有用,但它常常会带来大问题,在某些情况下,多重继承会引发歧义,从而导致无法编译。

典型的例子是“可怕的菱形问题”(或菱形问题),当一个类继承自两个同时拥有共同基类的其他类时,会产生歧义。

//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'

通过取消对 `Derived_1` 和 `Derived_2` 定义中 `virtual` 关键字的注释,该问题将得到解决,因为只会实现 `Base` 的一个副本。

//Output:

 = = = = = = = = = = = = = =
Ambiguous access sample:
Print base
Print Derived_1
Print Derived_2
Print Derived_3

正如我们刚刚看到的,多重继承可能存在风险。但是,为了减少多重继承的副作用,我们该怎么做呢?嗯,我们有多种技术可供选择

  • 组成
  • 聚合
  • CRTP
  • Mixin

注意:组合(Composition)是指创建由一个或多个简单对象组成复杂对象的过程,汽车就是一个经典例子,它被视为一个单一对象,但实际上它由各种对象组成:发动机、方向盘、车轮、螺丝等。

在这种情况下,关系是“拥有”(Has-a)。包含所有其他对象的复杂对象将需要管理这些更简单的对象,换句话说,被包含的对象只能作为包含它们的对象的功能而存在。

聚合

聚合(Aggregation)也总是指创建由一个或多个简单对象组成复杂对象的过程,但在这种情况下,复杂对象不管理聚合对象,而聚合对象独立于包含它们的对象而存在。

在这种情况下,对象之间的关系也是“拥有”(Has-a)类型。

CRTP

CRTP 用于为类添加通用 functionality。我们在第一部分已经对 CRTP 进行了介绍。

Mixin

Mixin 也用于为类添加通用 functionality。然而,一个 `Mixin` 类是从我们希望 `mixin` 类 functionality 能够添加到的对象类型派生出来的。CRTP 和 `Mixin` 通常以可变参数模板的形式使用。

注意:可变参数模板(Variadic template)是一个可以接受任意数量参数的模板类或函数。我们在本教程关于 Visual Studio 2019 为我们实现的 WinRT 运行时类(WinRT runtime classes)的第一个部分中已经介绍过可变参数模板。

在下面的 `CRTP` 与 `Mixin` 示例中,我们将看到最终输出的结果,无论是遵循 `CRTP` 还是 `Mixin` 模式,都是相同的。请记住,在这两种情况下,目的都是通过添加新功能来扩展给定类,同时尽量避免多重继承的陷阱。

我们将为已经具有 `PrintData()` 打印函数的类配备一个 `ReversePrint()` 函数。在 `UseCRTP` 命名空间中,实现 `ReversePrint()` 方法的 `Reverse` 类是一个模板类,它遵循 CRTP 习惯用法;在 `UseMixin` 命名空间中,它遵循 Mixin 习惯用法。下面,我们将更详细地介绍这两种模式之间的区别。

CRTP 与 Mixin

示例

#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

示例中 `CRTP` 和 `Mixin` 的主要区别

  • `CRTP`:`SuperHero` 类继承了 `Reverse<T>` 模板类。
  • `Mixin`:`SuperHero` 类不继承任何东西,保持不变,而模板类继承其参数的类(`T`)。
  • `CRTP`:需要在模板类的参数 `T` 和模板类本身之间进行类型转换。
  • `Mixin`:无需类型转换。
  • `CRTP`:在模板类中无需实现额外的专用构造函数。
  • `Mixin`:(在此例中)需要实现一个专用构造函数。
  • `CRTP`:在 `main` 函数中,直接使用 `SuperHero` 类。
  • `Mixin`:在 `main` 函数中,`SuperHero` 类是 `Reverse` 模板类的参数。

那么,何时使用 `CRTP`,何时使用 `Mixin` 呢?当我们要通过继承来扩展一个给定类,从而实现新的通用功能时,我们将使用 `CRTP`。需要扩展的类实际上必须派生自模板类,并且模板类必须以派生类本身作为其参数。

当我们想要为给定类添加附加功能,但又希望涉及的类保持独立时,我们将使用 `Mixin`。需要扩展的类不应派生自模板类;模板类应继承您主题的类。

汇总

我们用一个关于可变参数模板类和函数(variadic template classes and functions)的例子来结束对 C++ 一些特性的简要回顾。下面我们将看到,如何通过混合使用 `CRTP`、`Mixin` 和可变参数模板,可以以一种与“经典”继承不同的替代方式来扩展类。

最细心的人此时可能已经领悟到内在的矛盾:为了限制继承的问题,我们将使用……继承(尽管方式不同)。

可变参数模板类和函数示例

我之前介绍过可变参数模板;如前所述,这些是支持任意数量参数的类或模板函数。这项功能在开发非常复杂的应用程序时提供了极大的灵活性。

示例应用程序:第一个版本。(客户总是对的……也许)

假设我们接到客户委托,要求创建一个向其客户发送电子邮件的应用程序。项目所需的功能是

  • 撰写电子邮件
  • 发送它们

还假设,在接受委托后,我们提出了关于应用程序正确设计需要许多其他功能的合理建议:我们假设需要一个数据库来获取收件人数据,一个保存和显示这些数据的系统,等等。

最后,假设我们的客户不想听从建议,并坚持(并支付)只拥有上面描述的两个功能。仅此而已。在这种情况下,我们按照他想要的方式编写应用程序(毕竟,俗话说:客户永远是对的……),并给他试用。下面是我们应用程序的代码(第一个版本)。

#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?

 * * * * * * * * * * *

没有什么特别的,`CEmail` 类实现了 `IMessages` 和 `IDisplay` 接口。注意:在 `IMessages` 和 `IDisplay` 接口中,声明了纯虚函数;对于不记得的人来说,派生自抽象类和/或实现声明了纯虚函数的接口的类,必须实现纯虚函数。客户很高兴,过了一段时间,他指示我们为应用程序添加发送短信的功能,而不仅仅是电子邮件。我们去实现新功能……但方式不正确。注意:应用程序肯定能正常工作,但存在设计错误。下面是我们应用程序的代码(第二个版本)。

#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.

 * * * * * * * * * * *

这里也没有什么特别的,我们向项目中添加了一个新类,并在其中实现了通常的 `IMessages` 和 `IDisplay` 接口。

这样做,我们遵循了相同的初始设计方案。然而,不幸的是,在添加新功能时,我们做了不应该做的事情:我们复制了代码,事实上 `CSMS` 和 `CEmail` 类是相同的,甚至 `Display` 方法在两个类中几乎具有相同的代码。

遵循这个方案,当我们必须添加新功能时,对于其中的每一个,我们都将一遍又一遍地重写相同的代码。

很快,我们的代码将变得庞大,实际上无法维护。我们的项目出了问题。

与此同时,我们假设的应用程序已经售出不少,另一位客户指示我们扩展其功能,增加发送语音消息和图像的可能性,而不仅仅是电子邮件和短信。

研究案例,我们注意到这两个新功能无非是两种额外的消息类型。此时,我们需要一个通用机制来实现任何类型的消息,而无需每次都重写相同的代码。这些是可以通过**模板类和函数**解决的问题。

此时,然而,认识到原始项目不支持任何模板,我们只能从头开始审查应用程序的结构。这次,我们将依靠类和模板函数来设计应用程序。

在新项目中,我们希望每种类型的消息(类)都独立于其他消息,但所有类型的消息都有一个通用的管理器来处理类似的功能(`Send`、`Read` 等),该管理器负责将工作委托给每个消息类的更专业化的函数。

举个例子,每条消息都必须管理不同的发送函数。这是自然的,发送电子邮件的代码将与发送短信的代码不同。

因此,通用处理程序(一个模板类)将提供一个通用的发送方法,但也可以根据它是电子邮件还是短信(以及将来其他类型)调用特定的发送方法。我们按照以下 UML 图重新设计并重写应用程序。

从图中我们可以看到,我们只在(基本)`MessagesT` 模板类中实现了 `IMessages` 接口;后者是一个模板类,除了实现 `IMessages` 外,还继承了其参数(`T`)的类。(项目中的)它的参数是可变参数模板类 `MessageHelper`。正如我们在代码(如下)中看到的,`MessageHelper` 类继承了其模板参数(`D`)的类。

我们还可以看到定义可变参数模板类的语法(`typename...`)。`MessageHelper` 类将提供所有消息类型共有的功能:`Compose()`、`Send()` 和 `Read()`。

然而,如前所述,如果通用类 `MessageHelper` 的 `Send` 函数在需要一个通用的、适用于多种消息类型的 `Send` 函数时可以被调用,那么我们肯定还需要调用特定于每个消息类的专用 `Send` 版本。

我们通过向 `MessageHelper` 类添加一个名为 `SendSpecialized` 的可变参数模板函数来实现这一点。我们可以看到 `SendSpecialized` 可变参数模板函数的语法

    template <typename I, typename... Args>
    void SendSpecialized(I i, Args... args)
    {			
     I::Send(args...);
    }
    //......
    //......

我们只是声明了一个具有多个参数的模板函数,其中第一个是 `I`,其余(`Args...`)(或者更确切地说,其余的)是任意的。在函数定义中,`Args...` 是参数包,可以接受任意数量的参数。在函数体中,我们找到了函数的 `args` 参数,其中三点运算符(...)表示参数包展开。

此运算符(...)将参数包解包/展开为逗号分隔的参数,然后对每个参数,递归调用 `Function Send(args...)`。我们看到,与可变参数模板类一样,可变参数函数也使用称为省略号的运算符(...)。

我们将相同的逻辑应用于可变参数模板 `Displayable` 类,该类将为各种消息类提供特定的 `Display` 函数以及一个通用的 `Display()` 函数。

完成此操作后,我们声明前两种消息类型的类:`CEmail` 和 `CSMS`。

下面是我们应用程序的代码(第三个版本)。

#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.

    * * * * * * * * * * *

我们可以看到,在这个新版本的应用程序中,一方面我们避免了多重继承的问题,但以另一种形式保留了它;并且一些函数仍然是多态的。此外,在项目中引入可变参数模板使得理解未来任何新的消息类型变得更加容易。事实上,我们之前说过有必要重写(虚构的)应用程序,正是因为我们被要求添加其他类型的消息(语音和图像)。然而,这次添加新功能只需要 5 分钟。我们将另外两个类添加到项目中

//.........
//.........

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

 * * * * * * * * * * *

好了,现在我们的应用程序,由于其可重用的代码,肯定可以适应各种类型的消息。我们还看到了如何以及为何使用类/函数模板和可变参数模板;因此,我们可以认为对 C++ 一些重要特性的简要回顾已经结束,并继续进行示例UWP/WinRT/Win32 应用程序。

转至 第三部分:实现演示 C++ WinRT/Win32 应用程序

历史

  • 2022 年 1 月 14 日:初始版本
© . All rights reserved.