c++26新功能—indirect<T>和polymorphic<T>
一、indirect<T> 和 polymorphic<T>
indirect,间接的;polymorphic,多态。前者大家可能还有些生疏,但对后者应该是非常熟悉的。在C++26的模板支持中,提供了两个新类模板即indirect<T>和polymorphic<T>。主要目的是为了更完善的通过特化来实现具体的值语义。注意,是值语义。特别是后者,可以实现多态性质的值与其它标准库类型整体的组合,进而让编译器能够生成相关的函数和接口。
它们二者都是为动态分配对象的一种值语义控制的接口,其常量性都具有传染性。indirect<T>会持有T的对象并在复制indirect<T>时深拷贝T对象;polymorphic<T>有点类似于多态的控制,它持有一个T对象,但复制polymorphic<T>时会复制T的子类对象。
在C++的开发者中,处理多态一般是使用指针或引用。为了区别与指针等的不同,完善标准库中对值语义的相关内容,支持在复合类型中可以更简单方便的实现相关的代码。所以标准库中增加了对此二者的扩展。
二、具体的分析
在使用指针类型时,开发者可能会遇到一个问题,在pimpl中,如果一不小心会遇到非完全类型的的情况。也就是说,C++中提供了间接控制对象的指针类型。但在实际的开发过程中,其实也是需要对象的值类型的。特别是在存在继承的情况下,在标准库中的应用如果想控制值语义,需要通过一些技术方法才可能实现。
在C++26中完善了这方面的技术细节,通过indirect<T> 和 polymorphic<T>来达到相关的设计目的。特别是在复合类的设计中,其具有非常重要的意义 。正如上面据说,这两个类模板都具有常量传播和深拷贝的性质。除此之外,它们还具有一些特点:
- 无值状态
为了支持实现移动语义的相关控制,它们都有一个无值状态。达种状态下,如果进行了移动操作,则可以通过成员函数valueless_after_move来判断是否正常 - 分配器支持
二者均为分配器感知类型,所以其适合于此种类型的容器应用。如std::vector或std::map等 - 支持受限的契约扩展
即支持在运行时的先决条件下的契约开发,但operator-> 和 operator*两个运算符除外 - 提供了完美转发运算符
和optional和variant等一样,提供了完美转发的的相关运算符
需要提醒一下的是,要区别与indirect<T>和variant<Ts…>的不同,variant提供了多种可选类型之一的控制而indirect<T>则提供了单一类型的控制。而与智能指针相比,这两个类模板又提供了值语义和深拷贝且可以正确传播常量。
另外,polymorphic<T>需要派生类必须有公开的拷贝构造函数;其接口基类需要受保护的析构和拷贝构造函数。
三、应用场景
在标准的设计目的中,二者的应用主要有以下场景:
- 需要动态存储类型且类型已知的情况下,如上文提到的PIMPL、对象优化或减小头文件依赖等等
- 需要在运行时处理多种不同的子类并希望它们以值类型存储在同一容器中时,可以使用polymorphic<T>
- 一般是应用在复合类中
四、具体的例程
由于具体的标准尚未正式推出,所以看一下相关的提案中的例程:
struct A {enum class Constness { CONST, NON_CONST };Constness foo() { return Constness::NON_CONST; }Constness foo() const { return Constness::CONST; }
};class Composite {indirect<A> a_;Constness foo() { return a_->foo(); }Constness foo() const { return a_->foo(); }
};int main() {Composite c;assert(c.foo() == A::Constness::NON_CONST);const Composite& cc = c;assert(cc.foo() == A::Constness::CONST);
}// std::polymorphic
#include <memory>
#include <vector>
#include <iostream>class Shape {
public:virtual ~Shape() = default;virtual double area() const = 0;
};class Circle : public Shape {
private:double r_;
public:Circle(double radius) : r_(radius) {}double area() const override {return 3.14 * r_ * r_;}};class Rectangle : public Shape {
private:double w_, h_;
public:Rectangle(double width, double height) : w_(width), h_(height) {}double area() const override {return w_ * h_;}};int main() {std::vector<polymorphic<Shape>> vecShapes;vecShapes.push_back(Circle(3.0));vecShapes.push_back(Rectangle(2.2, 3.3));vecShapes.push_back(Circle(3.5));for (const auto& s : vecShapes) {s->area(); }return 0;
}
说明:代码仅供参考,无法保证可正常运行。
五、总结
在标准的学习中,一定要掌握其类似的库应用,比如本文介绍的两个类模板与variant的不同,二者与智能指针的不同,等等。重点是掌握它们的侧重点在哪里,它们如何能够互相完善C++中的各种场景的实现。就如同辨别孪生兄弟一样,只有掌握好细节的不同,才能更好的展开对技术的应用。