std::variant和std::visit

163 阅读1分钟

如果有一些不相关的类及其对象,它们并没有继承关系,但是可能拥有相同的接口定义,如何对这些对象在“运行时进行多态处理”?

0. 鸭子类型

鸭子类型是一种动态类型语言中的类型推断风格,根据对象的行为而不是类型来判断对象的特征。

“如果它走路像鸭子,叫声像鸭子,那么它一定就是一只鸭子。”

1. std::variant

std::variant是C++17引入的可以表示“多种类型”的类模版。一个std::variant类似于一个union对象,可以存储一个在类模版定义时指定的一种类型的对象。
使用std::variant来实现运行时多态,前提是事先可以明确一组类型。

using Anim = std::variant<Cat, Dog>; // Anim对象表示一个Cat或Dog
Anim a1 = Cat();
Anim a2 = Dog();

2. std::visit

std::visit可以调用std::variant中全部类型都拥有的“同名的函数”(公共接口)。std::visit的使用需要指定一个visitor lambda,来具体调用公共接口。

auto getSay = [](auto &anim){return anim.say();};
return std::visit(getSay, a1);  // 仅当Cat和Dog中都有say()方法,编译才会通过。

3.🌰例子

#include <iostream>

class Dog {
public:
    void swim() const {
        std::cout << "Dog can swim" << std::endl;
    }

    void say() const {
        std::cout << "Wang~" << std::endl;
    }
};

class Cat {
public:
    void climbTree() const {
        std::cout << "Cat can climb trees" << std::endl;
    }

    void say() const {
        std::cout << "Meow Meow~~~" << std::endl;
    }
};

using Anim = std::variant<Cat, Dog>;

int main() {  
    // 对象可以具体是variant声明时输入的类型的某一种
    Anim dog = Cat();

    // 可以通过std::visit来具体调用variant中全部类型都拥有的“同名的函数”(公共接口)
    auto getSay = [](const auto &anim){ return anim.say();};
    std::visit(getSay, dog);
    return 0;
}