1 分钟阅读

消息传递是一个非常有用的程序设计技术。利用消息传递,你可以编写出灵活的GUI代码,与GUI中种种时间打交道;你可以编写出模组,当实体被攻击时做出反击玩家的操作;总之,消息传递的重要性,不言而喻。

很可惜C++中暂无语言内置的消息传递机制,但是像C#中类似的代码你可以这么写(摘自Christian Nagel, Professional C# and .NET 2021 Edition):

using System;

public class CarInfoEventArgs : EventArgs
{
    public CarInfoEventArgs(string car) => Car = car;
    public string Car { get; }
}


public class CarDealer
{
    public event EventHandler<CarInfoEventArgs>? NewCarCreated;
    public void CreateANewCar(string car)
    {
        Console.WriteLine($"CarDealer, new car {car}");
        RaiseNewCarCreated(car);
    }

    private void RaiseNewCarCreated(string car) =>
        NewCarCreated?.Invoke(this, new CarInfoEventArgs(car));
}

这是买汽车的人:

public record Consumer(string Name)
{
    public void NewCarIsHere(object? sender, CarInfoEventArgs e) =>
        Console.WriteLine($"{Name}: car {e.Car} is new here");
}

调用代码(顶级语句):

CarDealer dealer = new();
Consumer sebastian = new("sebastian");
// 注册事件
dealer.NewCarInfo += sebastian.NewCarIsHere;
dealer.CreateANewCar("Ford Transit");

输出:

CarDealer, new car Ford Transit
sebastian: car Ford Transit is new here

方便吧?使用多播委托,可以轻松实现多个对象的广播,即,消息传递。

调用所有注册方法的顺序不一定相同。

但是C++没有呀?没关系,我们有“观察者模式”。

模块接口:

export module design_patterns.observer_pattern;
import <functional>;
import <map>;

export template <typename ...Arguments>
class Event 
{
    public:
        using Handle = std::uint32_t;
        using Observer = std::function<void(Arguments...)>;

        virtual ~Event(void) = default;

        [[nodiscard]]
        Handle operator+=(Observer observer);

        [[nodiscard]]
        Handle add(Observer observer);

        Event &operator-=(Handle handle);

        Event &remove(Handle handle);


        void raise(Arguments ...arguments);

        /**
         * @brief Raise the event. As same as raise() mem fn.
         */
        void operator()(Arguments ...arguments);

    private:
        std::map<Handle, Observer> observers;
        Handle observer_counter {0};
};

实现模块:

module design_patterns.observer_pattern;

import <utility>;

template <typename ...Arguments>
Event::Handle Event::operator+=(Event::Observer observer)
{
    return add(std::forward<Event::Observer>(observer));
}

template <typename ...Arguments>
Event::Handle Event::add(Event::Observer observer)
{
    auto current_counter {++observer_counter};
    observers[current_counter] = observer;
    return current_counter;
}

template <typename ...Arguments>
Event &Event::operator-=(Event::Handle handle)
{
    return remove(handle);
}

template <typename ...Arguments>
Event &Event::remove(Event::Handle handle)
{
    observers.erase(handle);
    return *this;
}

template <typename ...Arguments>
void Event::raise(Arguments ...arguments)
{
    for (auto &observer : observers)
        (observer.second)(arguments...);
}

template <typename ...Arguments>
void Event::operator()(Arguments ...arguments)
{
    raise(arguments...);
}

好啦!现在,跟之前C#中一样的,我们也可以写出类似的代码(导入模块什么的,省去了):

车交易者的模块接口文件:

export class CarDealer
{
    public:
        void createNewCar(std::string car_name);
        Event<std::string> &getEvent(void);
    private:
        Event<std::string> new_car_created_event;
};

对应的模块实现文件:

void CarDealer::createNewCar(std::string car_name)
{
    std::cout << std::format("CarDealer, new car {}", car_name) << std::endl;
    new_car_created_event.raise(car_name);
}

Event<std::string> &getEvent(void)
{
    return new_car_created_event;
}

接下来是买车的人。

模块接口文件:

export class Consumer
{
    public:
        Consumer(std::string full_name);
        void newCarIsHere(std::string car_name);
    private:
        std::string full_name;
};

对应的模块实现文件:

Consumer::Consumer(std::string full_name) :
    full_name {full_name}
{ }

void Consumer::newCarIsHere(std::string car_name)
{
    std::cout << std:format("{}: car {} is new here", full_name, car_name) << std::endl; 
}

请注意看接下来的调用代码:

int main(void)
{
    CarDeal dealer;
    Consumer sebastian {"sebastian"};

    dealer.getEvent() += std::bind(&Consumer::newCarIsHere, &sebastian, std::placeholders::_1);
    dealer.createNewCar("Ford Transit");

    return EXIT_SUCCESS;
}

输出类似。

注意,在对调用的dealer.getEvent()成员函数的返回值进行注册时,我们必须使用std::bind将一个生成的新函数提供给Event::add() / Event::operator+=()函数注册。其原因为C#的委托可以自动绑定到对应的对象,而C++内部使用对象指针this来跟踪调用母对象,因此需要将对象提供给std::bind()的第一个参数,而将第二个参数(不考虑对象指针,就是car_name参数)需要使用std::placeholders::_1来绑定。

有关对象指针的有关内容,请参阅《深度探索C++对象模型》,Stanley B. Lippman著。

实际上,C#中的event关键字就是一个语言内置的观察者模式,只不过通过多播委托和自动对象绑定,可以实现很大的简化。

最终完成于8/23日,此时玄关门口正下大暴雨。

分类:

更新时间: