当前位置: 首页 > news >正文

C++中的CRTP

CRTP,全称为 Curiously Recurring Template Pattern(奇异递归模板模式),是一种在C++中使用继承和模板技术来实现静态多态和功能复用的惯用法。它使用派生类来模板参数化基类,使得基类能够访问派生类,从而在编译期间就能实现特定的特化行为。

形式
下面是CRTP的一般形式:

template <typename T> 
class CuriousBase {
public:void interface() { // 接口方法static_cast<T*>(this)->imp(); // 强转成派生类类型,调用派生类的成员函数};
};class CuriousDerived : public CuriousBase<CuriousDerived> { 
public:void impl() { // 实现std::cout<< "in Derived::imp" << std::endl;  }
};

特点
1、基类是一个类模板,派生类是非模板类,它公有继承基类,同时自己也是基类的模板实参类型。基类虽然有派生类,但它却不是为多态设计的,因此析构函数可以不是virtual,而且一般也不会用它来创建具体对象。

2、在基类中定义“接口”方法,比如CuriousBase<T>::interface()成员函数,在派生类中定义它的“实现”,如派生类中的成员函数CuriousDerived::impl(),并在基类中调用该impl()。

3、为了调用派生类实现的impl(),在基类中的interface()函数内部使用static_cast对 this 指针进行了显式类型转换。对于基类CuriousBase<CuriousDerived> 而言,它仅有一个派生类CuriousDerived,再也没有别的派生类了,也就是基类类型和派生类类型是一一对应的,因此,它可以直接使用static_cast<CuriousDerived*>(this)把自己转换为CuriousDerived*类型。这种方式使得基类可以访问派生类的成员函数和数据,从而实现了(静态)多态性。

4、基类是模板类型,当使用不同的派生类来实例化基类模板时,得到的基类肯定是不同的类型。也就是说两个派生类不是继承自同一个基类的兄弟类,它们没有共同的基类,是没有任何关系的独立类,仅仅是提供了相同的成员函数而已。

类模板实例化过程

CRTP中既然带有Curiously字眼,它形式上看起来也确实很“奇异”:基类是一个类模板,而基类的派生类又是它的模板实参。我们知道,要定义派生类肯定需要知道基类的类型,而在使用类模板来实例化一个模板类时又得需要知道模板的实参类型,在这里实例化基类时的实参类型又是派生类的类型。这样就存在着循环依赖:定义派生类时需要知道基类,而实例化基类时又必须知道它的派生类。先有蛋还是先有鸡?模板类又是怎么进行实例化的呢?

就以下面的代码为例,说一下实例化过程:

CuriousDerived d;
d.interface(); 

当编译器遇到CuriousDerived d;语句时,发现类CuriousDerived继承自基类CuriousBase,而CuriousBase是一个模板类,因此首先要实例化这个基类模板。我们知道,模板在进行实例化时,需要进行两阶段编译,第一阶段编译忽略模板参数,只检查模板代码自身的正确性,第二阶段再把模板实参类型代入,再进行编译。因此,对于CuriousBase类模板,第一次编译时,忽略模板参数,即:

class CuriousBase {
public:void interface() {static_cast<T*>(this)->impl();};
};

显然除了模板参数T之外,没有未知的类型和其它形式的错误,第一次编译没有问题,注意,此时没有对成员函数interface()进行实例化,按照规则约定,只有在用到类模板的成员函数时,才会进行实例化。

第二次编译时,把模板实参类型CuriousDerived代入,创建了CuriousBase<CuriousDerived>类。此时尽管CuriousDerived是一个incomplete类型,可能仅仅是一个名称而已。在CuriousBase 也没有使用CuriousDerive定义数据成员,只是使用它作为一个指针类型,并不需要知道它的具体定义细节,可以对模板实例化出一个基类类型CuriousBase<CuriousDerived>。有了基类,接下来派生类CuriousDerived也就可以正常定义了。

当编译语句d.interface()时,即此时用到基类中的interface()成员函数了,开始对基类中的成员函数interface()进行实例化:

void interface() {static_cast<T*>(this)->impl();
};

使用模板实参CuriousDerived对CuriousBase::interface()进行实例化,最终生成了一个成员函数:

void interface() {static_cast<CuriousDerived*>(this)->impl();
};

至此,模板基类和它的模板函数实例化完成。

应用场景
CRTP应用场合是实现一些套路化的工作,根据对外提供服务的类型是基类类型,还是派生类类型,可以把CRTP的应用形式分为两种情景:一种是使用CRTP的基类类型,关注的是静态多态机制;另一种是使用CRTP的派生类类型,关注的是功能复用。不管在哪种形式下使用,都是使用派生类类型来创建对象。

1、静态多态-使用基类类型作为对外的接口参数

这种场景使用基类作为对外接口,基类接口实现了一个套路化的接口,即在某个步骤中调用了派生类的函数,派生类并不使用基类的功能接口。

本场景类似设计模式中的模板方法模式,不过这里是使用静态多态机制实现的。因为基类中interface()方法已经按照套路实现了框架流程,只要派生类按照自己的业务需要,定制好impl()的实现就行了。参照模板方法模式的定义,基类中的interface()是模板方法,而派生类中impl()是钩子方法

应用形式是把基类类型作为一个函数的参数,在函数中调用基类提供的接口。如下面的例子:

template <typename T> 
class CuriousBase {
protected:CuriousBase() = default; // 防止使用基类创建对象
public:void interface() { // 接口static_cast<T*>(this)->impl();};
};// 定义两个派生类
class CuriousDerived1 : public CuriousBase<CuriousDerived1> { 
public:void impl() { // 实现std::cout<< "in Derived1::impl" << std::endl;  }
};class CuriousDerived2 : public CuriousBase<CuriousDerived2> { 
public:void impl() { // 实现std::cout<< "in Derived2::impl" << std::endl;  }
};// 一个函数以基类类型CuriousBase作为参数
template<typename T>
void use_crtp(CuriousBase<T> &t) {t.interface();
}void test() {CuriousDerived1 d1;CuriousDerived2 d2;use_crtp(d1);use_crtp(d2);
} 

函数use_crtp是一个函数模板,它的模板参数就是基类的模板参数,因为不同参数类型实例化后的基类是不同的类型,形成了use_crtp的多个重载版本,编译器在编译时是静态绑定。它无法像动态多态那样,使用一个公共的基类引用类型来访问各个不同的子类类型,而是编译器根据每个派生类类型实例化出不同的重载版本,因为重载也是C++实现多态机制的一种,因此,CRTP的这种应用场景一般称为静态多态。

这种多态性可以认为是一种契约驱动的设计:CRTP基类CuriousBase<T>约束了参数的类型,即必须提供interface()成员函数,而模板又对派生类的行为做了约束,必须提供impl()成员函数。对于函数的实参,就约束了它必须是CuriousBase<T>的派生类。如果这个契约没有履行,编译器会无法编译。这里相当于c++20中提出的concept的形式,为一个函数约束了接口类型:CuriousBase<T>的派生类。

如果要求必须通过CuriousBase类调用接口interface(),为了防止CuriousDerive类创建的对象能够绕过interface(),直接调用impl(),可以在派生类中把impl()使用private修饰,并把CuriousBase声明成CuriousDerive类的友元类,这样CuriousDerive对象就无法访问它了。

2、功能混入mixin-使用派生类作为对外的接口

直接使用派生类创建的对象,派生类复用了基类所实现的功能,实际上这是一种混入(Mixin)机制,即通过public继承一个模板类来为自己添加功能。当然CRTP混入要特殊一些,基类在实现功能时也要借助于派生类实现的功能,即基类中通过把this指针转换为派生类类型来调用派生类的成员函数。目的就是派生类只要自己实现了某个成员函数,其它使用这个成员函数来完成一些功能的成员函数,就不再需要自己开发了,直接继承基类,复用基类提供的功能就可以了。

我们知道,在C++中,一个类继承另一个类即基类,也能实现功能复用,但是基类实现的功能只是针对自己(基类)这个类型,也就是它所用到的资源只能是自己,这是和CRTP模式的区别。如果要实现的功能需要知道它的派生类类型,或者调用派生类的成员函数,常规的继承是无法实现的,因此可以使用CRTP这种实现惯例来实现这种要求。

我们看一个派生类复用基类功能的例子。

template <typename Derived>
struct add_postfix_increment {Derived operator++(int) {auto& self = static_cast<Derived&>(*this);Derived tmp(self);++self;return tmp;}
};template <typename Derived>
struct not_equals {bool operator!=(const Derived &other) {auto self = static_cast<Derived*>(this); return !(*self == other);}
};

定义了两个CRTP基类:add_postfix_increment 类用于为派生类提供一个++后缀的操作符,它要借助于派生类的++前缀操作符实现这个功能,not_equals类用于为派生类提供不相等!=操作符的功能,它要借助于派生类的相等==操作符的功能。一个类只要提供了前缀++和相等==操作符的成员函数,就可以不用编写后缀++和不相等!=操作符的实现,直接公有继承add_postfix_increment类和not_equals类就可以了。

下面是一个某个派生类的实现:

struct some_type : add_postfix_increment<some_type>, not_equals<some_type> {some_type(int x) : x(x) {}// 这是前缀递增,后缀递增依靠它实现some_type& operator++() {x++;return *this;}bool operator==(const some_type &other) {return x == other.x;}using add_postfix_increment<some_type>::operator++;void print() {cout << x << "\n";}private:int x;
}; 

some_type类实现了前缀operator++操作符和相等operator==操作符,并且以自己作为模板参数公有继承了add_postfix_increment类和not_equals类。这样some_type复用了两个基类的功能,也就是为some_type混入(mixins)了add_postfix_increment类和not_equals类所提供的功能。

注意some_type类中的声明语句:using add_postfix_increment<some_type>::operator++;该语句是说明some_type委托使用基类的operator++成员函数,因为some_type类自己定义了前缀operator++成员函数,它们的名称相同,子类会隐藏父类的同名成员函数,如果没有这句using声明,当调用后缀operator++操作符时,会因为找不到对应的操作符函数而无法编译。

下面是简单的测试程序:

void foo() {some_type st(42);++st; // ++前缀操作,是some_type自己的成员函数st.print();st++; // ++前缀操作,是some_type继承自基类add_postfix_increment的成员函数st.print();
}void bar() {some_type st1(41);some_type st2(42);cout << (st1 == st2) << "\n"; //==操作,是some_type自己的成员函数cout << (st1 != st2) << "\n"; //!=操作,是some_type继承自基类not_equals的成员函数st1++;cout << (st1 == st2) << "\n";cout << (st1 != st2) << "\n";
}

注意事项
1、 基类中有调用派生类成员函数的函数,不然使用CRTP没有多大的意义了。因此,CuriousBase在调用派生类中的成员函数时,必须要强转为派生类类型,否则,如果基类和派生类有相同的成员函数,调用的是基类中成员函数。

2、 从基类类型转换为派生类类型时,要使用指针或者引用形式的类型转换,不能使用值类型的转换,如果这样:static_cast<T>(*this).imp(); 会有类型转换函数的调用,要把基类类型的对象转换为派生类类型的对象,显然是不可以的,会编译失败,应该用指针类型:static_cast<T*>(this)->imp()或者引用类型:static_cast<T&>(*this).impl();。

3、在基类中,模板参数不能用来定义基类的数据成员。
我们知道,对于一个类模板,模板参数可以用来定义这个类的数据成员,比如下面示例代码:

template <typename T> 
class CuriousBase {T data; // A
public:void interface() {T obj; // B}
};

但是在CRTP中,不允许这么做,因为此时无法实例化基类模板。也就是说基类 CuriousBase 的对象布局不能依赖于它的模板参数 CuriousDerive,因为当类CuriousBase<CuriousDerive>进行模板实例化时,类型CuriousDerive还是incomplete类型,还不知道它的对象布局。

在示例代码中,A处使用模板参数类型T定义了一个成员变量T data。前面说过,由于基类和派生类之间存在循环依赖,此时要知道基类的布局,就得要知道T的具体类型,但T是一个incomplete类型,所以无法编译。当然,既然基类实例化时T还是incomplete类型,那么在A处改为T *data,也能通过编译,不过在基类中用它的一个派生类定义自己的一个数据成员意义不大,CRTP的用途也不在这种场景。

但在B处,在成员函数里面使用模板参数类型T定义了一个局部变量,因为在实例化interface()成员函数时,类CuriousBase<CuriousDerived> 已经实例化,类型CuriousDerived 已经定义成功,是一个complete类型,也就是T的类型此时已经定义出来了。

A处是指在定义对象时对类模板进行实例化,此时数据成员的类型必须要知道,这样才能知道类的内存空间布局;而B处成员函数模板是在调用函数时才实例化,等到成员函数interface调用时,此时CuriousDerive对象已经创建,类型也是已知的,可以成功实例化interface函数。

4、 如果基类中也有impl()成员函数,会被派生类中的同名impl()成员函数隐藏(注意,仅仅是函数名称相同也会隐藏),编写代码时要注意,以免发生错误。从add_postfix_increment 类和some_type类的例子可以看到这一点,通常可以在派生类中使用委托基类成员函数的方法来解决函数名隐藏问题。

5、基类的模板参数类型必须是它的派生类,如果不是CuriousBase的派生类,在调用interface()时,会编译失败。

class misc { 
public:void imp() {std::cout<< "in Derived::impl" << std::endl;  }
};CuriousBase<misc> base; // 正常编译
base.interface(); // 编译失败

语句base.interface();会编译失败,因为在base的interface()成员函数里面,有static_cast派生类类型的操作,misc不是CuriousBase<misc>的子类类型。

6、不要使用基类来创建对象。
因为CRTP基类中肯定要用到派生类中的方法,CuriousBase类对象不应该作为独立的对象存在。因此,在创建对象时,不要使用CRTP基类类型来定义对象,而是要用它的派生类来定义对象,否则在使用接口函数时可能会有未定义UD的行为。比如:

CuriousBase<CuriousDerived> base;
base.interface();

这样base对象里面只有CuriousBase的subobject部分,并没有CuriousDerived的成员部分,运行结果可能是UD的。比如,假设派生类中有自己的数据成员:

class CuriousDerived : public CuriousBase<CuriousDerived> {string str ="12345";
public:void impl() {std::cout<< "in Derived::impl, " << str << std::endl;  }
};

执行时interface()接口调用的是CuriousDerived中的impl()接口,但是因为创建的对象实例是CuriousBase<CuriousDerived>类型的,只是一个subobject,对象中并没有数据成员str,在调用CuriousDerived::impl()时会访问一个不存在的str数据,发生异常,程序崩溃。

因此,为了防止此意外发生,最好对CuriousBase类的实现在形式上做出限制,可以把CuriousBase的构造函数声明为protected形式的,这样可以防止使用基类来创建对象,能创建对象的只能是它的派生类。

7、既然CRTP不是为了实现动态多态,因此基类一般没有virtual函数,也不会virtual析构函数,如果派生类有自己的资源,需要自己管理这些资源的回收及销毁。

可以考虑这样实现:

~CuriousBase() {static_cast<T*>(this)->cleanup();
}void cleanup(){}

如果派生类需要释放资源时,可以提供一个cleanup()函数,当然基类同时也实现一个缺省的cleanup()函数,这样如果派生类没有资源要释放,可以不用提供它,这个缺省函数就可以供基类自己使用,以免无法编译。此外,还得要约定好,如果派生类有自己的资源需要释放,必须实现一个cleanup()函数,并在里面释放资源。

如果基类使用下面的实现,在它的析构函数中调用了派生类的析构函数,是无法达到销毁子类对象的目的,会有一个严重的bug。大家可以想想为什么?

template <typename T> 
class CuriousBase {
public:......~CuriousBase() {static_cast<T*>(this)->~T();}
};

模板参数化this指针

最后,我们再回过头来再看一下CRTP基类模板的形式,发现使用模板的目的是为了能够把基类类型转换成派生类类型:

template <typename T> 
class CuriousBase {
public:void interface() { // 接口方法static_cast<T*>(this)->impl(); // 强转成派生类类型,调用派生类的成员函数};
};

基类虽然是一个类模板,但是模板参数T并没有用来定义类中的数据成员,前面分析过,此情况下会存在”先有鸡还是先有蛋“的问题,是无法编译的,不可能有这种情况;其次,它也没有用作成员函数中的参数类型,否则没必要把类定义成类模板,而是把相关的成员函数定义成函数模板就可以了。

其实,从CRTP基类的形式上也能看出,模板参数T仅用在了它的某个成员函数内部:把this指针转成派生类类型。既然如此,我们能否根据这个特点,简化一下基类的定义,让它不再是类模板,而是把它的成员函数改成模板呢?比如下面的定义:

class CuriousBase {
public:template <typename T> void interface() { // 接口方法是函数模板static_cast<T*>(this)->impl(); // 强转成T类型,调用它的成员函数};
};class CuriousDerived : public CuriousBase { 
public:void impl() { // 实现std::cout<< "in Derived::imp" << std::endl;  }
};

把模板由类改到类的成员函数上,这样在调用这个成员函数时,指定它的参数类型。编一段测试代码试试:

int main() {CuriousDerived d;d.interface<CuriousDerived>(); 
}

能正常工作,貌似可行。
不过,代码d.interface<CuriousDerived>()看起来并不优雅,如果不加上模板类型参数,这样来使用:d.interface(),是无法编译的,编译器无法根据实参类型自动推导。根本原因就在于模板类型在这里是用在this指针参数上面,而this指针在成员函数中是隐含参数,无法显式地传参,也就无法让编译器自动推导这个隐含的参数类型。因此,虽然d明明就是CuriousDerived类型,但在调用interface()函数时还得必须显式地指定类型CuriousDerived才能使用,有点多此一举。

那么,能不能把这个显式的模板实参类型去掉呢?

好在C++23为成员函数引入了this显式参数,既然this参数可以在成员函数中作为一个参数显式出现,那就可以把这个this参数定义为模板参数了,这也就为实现上面的例子带了方便。

下面看一下实现:

class CuriousBase {
public:template <typename T> void interface(this T &derive) { // 接口方法derive.impl();};
};class CuriousDerived : public CuriousBase { 
public:void impl() { // 实现std::cout<< "in Derived::impl" << std::endl;  }
};

测试代码:

int main() {CuriousDerived d;d.interface(); 
}

显然,和正常的调用模板一样,无需再显式指定实参类型了。为了支持更多的调用语义,使用转发引用的形式来定义模板成员函数:

class CuriousBase {
public:template <typename T> void interface(this T &&derive) { // 使用转发引用的形式来传递this参数derive.impl();};
};

这样,可以支持多种形式的调用:

int main() {CuriousDerived d;d.interface(); // 左值类型CuriousDerived().interface();  // 右值类型CuriousDerived *p = new CuriousDerived;p->interface(); // 指针类型delete p;
}

CRTP基类是类模板,基类实例化之后,和派生类是一一对应的,即一个基类只能派生一个子类,而模板参数化this指针的实现方式是基类只有一个,而且是非模板类,它的派生类可以有多个,但是每定义一个派生类,在该基类中都会使用派生类类型实例化出一个对应的interface()函数,也就是在基类中会有多个interface()成员函数和它的派生类一一对应。

相关文章:

C++中的CRTP

CRTP&#xff0c;全称为 Curiously Recurring Template Pattern&#xff08;奇异递归模板模式&#xff09;&#xff0c;是一种在C中使用继承和模板技术来实现静态多态和功能复用的惯用法。它使用派生类来模板参数化基类&#xff0c;使得基类能够访问派生类&#xff0c;从而在编…...

go压缩的使用

基础&#xff1a;使用go创建一个zip func base(path string) {// 创建 zip 文件zipFile, err : os.Create("test.zip")if err ! nil {panic(err)}defer zipFile.Close()// 创建一个新的 *Writer 对象zipWriter : zip.NewWriter(zipFile)defer zipWriter.Close()// 创…...

一图解千言,了解常见的流程图类型及其作用

在企业管理、软件研发过程中&#xff0c;经常会需要进行各种业务流程梳理&#xff0c;而流程图就是梳理业务时必要的手段&#xff0c;同时也是梳理的产出。但在不同的情况下适用的流程图又不尽相同。 本文我们就一起来总结一下8 种最常见的流程图类型 数据流程图 数据流程图&…...

【微信小程序_19_自定义组件(1)】

摘要:本文主要介绍了小程序开发中自定义组件的相关知识。包括组件的创建与引用,可在项目根目录创建组件文件夹,生成相应文件,并根据使用频率选择全局或局部引用。还阐述了组件和页面的区别,如组件的.json 文件需声明 “component: true”,.js 文件调用 Component () 函数…...

标准版admin后台页面添加及开发操作流程及注意事项

基础介绍 CRMEB后台管理是基于vue2技术栈进行开发搭建的 Vue Router 使用的是v3版本&#xff0c;mode为history模式 如需修改 mode 请在src/setting.js中修改routerMode 新建页面 新建路由 根据目录结构&#xff0c;需要在src/router/modules中对应模块中&#xff0c;添加对…...

‘perl‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。

‘perl’ 不是内部或外部命令,也不是可运行的程序 或批处理文件。 明明已经根据教程安装了perl环境,但是在cmd中依赖报该错误,本章教程提供解决办法。 一、激活perl环境 state shell ActiveState-Perl-5.36.0此时输入perl -v 是可以直接输出perl版本号的。 二、找到perl的执…...

如何利用CMMI帮助组织消除低价值流程

CMMI发展到今天&#xff0c;过程中历经了不断的蜕变和升级。从早期的CMM到今天的CMMI3.0&#xff0c;从早期的22个过程域优化组合到今天的20个实践域&#xff0c;从早期隶属的SEI到今天的CMMI研究院&#xff0c;所有的变化都是与时俱进&#xff0c;都是为了提供更好的实践&…...

如何理解线程安全这个概念?

文章目录 为什么需要线程安全&#xff1f;线程安全的实现方式总结推荐阅读文章 线程安全&#xff08;Thread Safety&#xff09;是指在多线程环境中&#xff0c;多个线程同时访问某个对象时&#xff0c;不会导致程序出现错误的状态或不一致的结果。简单来说&#xff0c;线程安全…...

代码随想录算法训练营第48天| 739. 每日温度,496.下一个更大元素 I,503.下一个更大元素II

第十一章&#xff1a;图论part01 图论理论基础 大家可以在看图论理论基础的时候&#xff0c;很多内容 看不懂&#xff0c;例如也不知道 看完之后 还是不知道 邻接矩阵&#xff0c;邻接表怎么用&#xff0c; 别着急。 理论基础大家先对各个概念有个印象就好&#xff0c;后面在…...

Qt 支持打包成安卓

1. 打开维护Qt&#xff0c;双击MaintenanceTool.exe 2.登陆进去,默认是添加或移除组件&#xff0c;点击下一步&#xff0c; 勾选Android, 点击下一步 3.更新安装中 4.进度100%&#xff0c;完成安装&#xff0c;重启。 5.打开 Qt Creator&#xff0c;编辑-》Preferences... 6.进…...

PDF工具类源码

PDF-Guru: PDF Guru Anki是一款以PDF为中心的多功能办公学习工具箱软件&#xff0c;包含四大板块功能&#xff1a;PDF实用工具箱、Anki制卡神器、Anki最强辅助、视频笔记神器&#xff0c;软件功能众多且强大&#xff0c;熟练运用可以大幅提高办公和学习效率&#xff0c;绝对是您…...

NirCmd-Gui-Chinese-Introduction

简介 此程序是我的一个练习作品&#xff0c;单纯是为了提升编程水平&#xff0c;次要是为了做一个NirCmd的Gui&#xff0c;其实主要成分还是Gui&#xff0c;核心代码就两三行。 主要是Gui&#xff0c;功能基于nircmd.exe实现&#xff0c;程序本身不提供一些重要的功能。 关于…...

吴恩达深度学习笔记(7)

误差分析&#xff1a; 你运行一个算法代替人类计算&#xff0c;但是没有达到人类的效果&#xff0c;需要手动检查算法中的错误&#xff0c;对模型的一些部分做相应调整&#xff0c;才能更好地提升分类的精度。如果不加分析去做&#xff0c;可能几个月的努力对于提升精度并没有…...

二、数据离线处理场景化解决方案

https://connect.huaweicloud.com/courses/learn/Learning/sp:cloudEdu_?courseNocourse-v1:HuaweiXCBUCNXE147Self-paced&courseType1 1.离线处理方案 **业务场景-安平领域** 业务场景-金融领域 离线批处理常用组件 HDFS&#xff1a;分布式文件系统&#xff0c;为各种…...

算法题总结(十四)——贪心算法(上)

贪心算法 什么是贪心 贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 贪心的套路&#xff08;什么时候用贪心&#xff09; 刷题或者面试的时候&#xff0c;手动模拟一下感觉可以局部最优推出整体最优&#xff0c;而且想不到反例&#xff0c;那么就试一试…...

hive on tez 指定队列后任务一直处于running状态

如上图所示一直处于running状态&#xff0c;查看日志发现一直重复弹出同一个info&#xff1a; 2024-10-18 16:57:32,739 [INFO] [AMRM Callback Handler Thread] |rm.YarnTaskSchedulerService|: Allocated: <memory:0, vCores:0> 释义: 当前应用程序没有分配到任何内存…...

闲说视频清晰度和各种格式、编码技术的发展历史

文章目录 引子清晰度视频格式&#xff1a;MP4、AVI 、MKV、MOV、WMV、FLV 、RMVB等等什么是视频格式MP4AVIMKVMOVWMVFLVRM / RMVB其他 编码技术&#xff1a;MPEG-1、MPEG-2、MPEG-4、RealVideo、DivX、XviD、H.264&#xff08;AVC&#xff09;、H.265&#xff08;HEVC&#xff…...

嵌入式职业规划

嵌入式职业规划 在嵌入式的软件开发中&#xff0c;可以分为&#xff1a; 嵌入式MCU软件开发工程师&#xff1b; 嵌入式Linux底层&#xff08;BSP&#xff09;软件开发工程师&#xff1b; 嵌入式Linux应用开发工程师&#xff1b; 嵌入式FPGA算法开发工程师 对于前两个阶段 …...

Nginx - 实现 TCP/DUP流量的按 IP 动态转发

文章目录 需求背景需求目标&#xff1a;使用场景&#xff1a;成功标准&#xff1a;技术要求&#xff1a; Ng配置测试验证 需求 Nginx Stream TCP 协议按 IP 转发 背景 为了优化网络性能和提升服务的可用性&#xff0c;我们需要在 Nginx 中配置 stream 模块&#xff0c;使其根…...

基于深度学习的进化神经网络设计

基于深度学习的进化神经网络设计&#xff08;Evolutionary Neural Networks, ENNs&#xff09;结合了进化算法&#xff08;EA&#xff09;和神经网络&#xff08;NN&#xff09;的优点&#xff0c;用于自动化神经网络架构的设计和优化。通过模拟自然进化的选择、变异、交叉等过…...

软考-软件设计师(10)-专业英语词汇汇总与新技术知识点

场景 以下为高频考点、知识点汇总。 软件设计师上午选择题知识点、高频考点、口诀记忆技巧、经典题型汇总: 软考-软件设计师(1)-计算机基础知识点:进制转换、数据编码、内存编址、串并联可靠性、海明校验码、吞吐率、多媒体等: 软考-软件设计师(1)-计算机基础知识点:进制…...

PyTorch 2.5 发布带来一些新特性和改进

官网&#xff1a;https://github.com/pytorch/pytorchGitHub&#xff1a;https://github.com/pytorch/pytorch原文&#xff1a;https://github.com/pytorch/pytorch/releases/tag/v2.5.0 主要亮点 (Highlights)] SDPA CuDNN 后端&#xff1a;为 torch.nn.functional.scaled_d…...

算法:560.和为k的子数组

题目 链接:leetcode链接 思路分析&#xff08;前缀和&#xff09; 注意&#xff1a;我们前面讲过滑动窗口可以处理子数组、子串等问题&#xff0c; 但是在这道题目里面注意数据范围 -1000 < nums[i] < 1000 nums[i]可正可负&#xff0c;区间的和没有单调性&#xff0c;使…...

C++之list(2)

list(2) list的迭代器 const迭代器 根据我们之前学过的知识&#xff1a; const int*p1;//修饰的是指向的内容 int *const p2;//修饰的是迭代器本身我们写const迭代器&#xff0c;期望的是指向的内容不能修改。 所以更期望写上面p1的形式 const迭代器与普通迭代器的不同点在于…...

React Componet类组件详解(老项目)

React类组件是通过创建class继承React.Component来创建的&#xff0c;是React中用于构建用户界面的重要部分。以下是对React类组件的详细解释&#xff1a; 一、定义与基本结构 类组件使用ES6的class语法定义&#xff0c;并继承自React.Component。它们具有更复杂的功能&#…...

位运算题目-Java实现-LeetCode题解:判断字符是否唯一-丢失的数字-两整数之和-只出现一次的数字 II-消失的两个数字

这里是Themberfue 上一篇文章讲完了常见位运算的技巧以及总结 那么本章则通过五道题来运用这些技巧 判定字符是否唯一 题目解析 本题要求判断给定字符串中的字符是否唯一&#xff0c;也就是每个字符是否只出现一次 算法讲解 本题用哈希表遍历每一个字符也可以解决 如果这题使…...

复合泊松过程

复合泊松过程的均值、方差与特征函数 复合泊松过程的定义 复合泊松过程 ( Y(t) ) 是一种常见的随机过程&#xff0c;通常定义为&#xff1a; Y ( t ) ∑ k 1 N ( t ) X k Y(t) \sum_{k1}^{N(t)} X_k Y(t)k1∑N(t)​Xk​ 其中&#xff1a; ( N(t) ) 是一个强度为 ( \lambd…...

[week1] newstar ctf ezAndroidStudy

本题主要考查对 APK 基本结构的掌握 查看 AndroidManifest.xml 可以发现 activity 只有 Homo 和 MainActivity 我们用 Jadx 打开 work.pangbai.ezandroidstudy.Homo 就可以获得 flag1 打开 resources.arsc/res/value/string.xml 搜索 flag2 即可 按描述到 /layout/activity_ma…...

TCP——Socket

应用进程只借助Socket API发和收但是不关心他是怎么进行传和收的 数据结构 图示Socket连接 捆绑属于隐式捆绑...

OpenStack服务Swift重启失效(已解决)

案例分析Swift重启失效 1. 报错详情 在重新启动 VMware 虚拟机后&#xff0c;我们发现 OpenStack 的 Swift 服务出现了 503 Service Unavailable 错误。经过排查&#xff0c;问题根源在于 Swift 服务所使用的存储挂载是临时挂载&#xff0c;而非永久挂载。 Swift 服务依赖于…...