先看一个例子:
class Test{
private:
int ma;
public:
Test(int a = 0) : ma(a) { cout << "Test(int a)" << endl; }
~Test() { cout << "~Test" << endl; }
Test(const Test &t)
{
ma = t.ma;
cout << "Test(const Test&t)" << endl;
}
Test &operator=(const Test &t)
{
ma = t.ma;
cout << "Test& operator=(const Test&t)" << endl;
}
};
C++编译器对于对象的构造优化:用临时对象生成新对象的时候,临时对象就不产生了,直接构造新对象就可以了,而不是先构造临时对象,然后拷贝构造给新定义的对象
eg:
Test t2(t1);//拷贝构造
Test t3=t1; //拷贝构造 因为t2 ,t3都是未定义的
Test t4=Test();//和Test t4()是没有区别的,并不是先构造一个
//临时对象然后拷贝构造给t4
而对于已有对象进行构造赋值的时候,就是构造临时对象,然后调用赋值语句
eg:
Test t3=t1; //拷贝构造 因为t2 ,t3都是未定义的
t3=Test(20); //构造临时,赋值
这个~Test就是产生的临时对象Test(20)的析构
eg:
显示生产临时对象,隐式生成临时对象
Test t1;
Test t2(t1);//拷贝构造
Test t3=t1; //拷贝构造 因为t2 ,t3都是未定义的
Test t4=Test();//和Test t4()是没有区别的,并不是先构造一个
//临时对象然后拷贝构造给t4
t3=(Test)30; //int ->Test(int) 显示
t3=50; // int ->Test(int) 隐式 ,编译器都要看有没有Test(int)的构造函数
eg:
指针不要指向临时对象,因为临时对象会析构,指针指向一个已经析构的对象,这样不安全
引用可以指向临时对象,因为是别名
Test *p=&Test(55); //高版本编译器已经报错
const Test &ps=Test(60);
所以指针这种都是要new一个出来,最后delete
Test* p = new Test(55);
delete p;
堆上分配的内存,不delete,是不会自动析构的,会泄漏内存
在实现string的时候对于拷贝构造和赋值,指针都需要都产生临时对象,不断的new delete效率很低,所以优化的时候引用右值拷贝,赋值
对于vector的push_back,你给他传左值,他就调左值的拷贝构造,你给他传右值,就会调右值的拷贝构造.vector怎么实现的呢?
ans: 在外面传的是用过模板类型推导和引用折叠+完美转发实现
注意右值引用变量其实是一个左值
int &&b=20;
int &c=b;
拷贝构造其实就是初始化的方式
什么是初始化的方式? 就是式子左边的还没有定义出来
xxx t1=已定义的或者匿名临时对象
xxx t1(已定义的或者匿名临时对象)
赋值方式就是,左边,右边都已经定义存在了
xxx t1
xxx t2
t1=t2; 赋值
emplace
像STL里面emplace系列的api都是通过可变参模板和引用折叠,forward实现的
// emplace_back方法
template <typename... Args>
void emplace_back(Args&&... args) {
if (size_ >= capacity_) {
// 扩容
reserve(capacity_ * 2);
}
// 在尾部直接构造对象
new (data_ + size_) T(std::forward<Args>(args)...);
++size_;
}
// emplace方法
template <typename... Args>
void emplace(size_t pos, Args&&... args) {
if (pos > size_) {
throw std::out_of_range("Invalid position");
}
if (size_ >= capacity_) {
// 扩容
reserve(capacity_ * 2);
}
// 后移元素
for (size_t i = size_; i > pos; --i) {
data_[i] = std::move(data_[i-1]);
}
// 在指定位置直接构造对象
new (data_ + pos) T(std::forward<Args>(args)...);
++size_;
}
可变参模板部分补充
类模板的友元_模板友元
可变参数模板_c++可变参数模板类_右大臣的博客-CSDN博客
下面是对我这两个文章的一点补充
template<typename ...Args>
void add(Args&&... args)
{
((cout<<"args:"<<forward<Args>(args)<<endl),...);
}
int main()
{
add(12,3,54,5);
getchar();
return 0;
}
这里使用了折叠表达式 `(expression, ...)` 来展开参数包,并在每次展开时输出参数的值。这样,我们可以处理任意数量的参数,而不仅仅限于一个参数。
请注意,折叠表达式 `(expression, ...)` 是C++17引入的语法,它允许将一系列表达式以逗号分隔的形式展开。
平常我们自己写可能最多的是这么写的
template<typename T>
void add(T&& arg)
{
std::cout<<"accept last args"<<endl;
std::cout << arg << " ";
}
template<typename T, typename... Args> //
void add(T&& arg, Args&&... args)
{
std::cout << arg << " ";
add(std::forward<Args>(args)...); //forward不是必须的,这里只是做个完美转发
}
int main()
{
add(12,3,54,5);
getchar();
return 0;
}
第一个add
函数是一个递归的终止条件,它用于处理参数包中只有一个参数的情况。当参数包中只剩下一个参数时,递归调用将停止,这时只剩下最后一个参数需要处理。
在这个例子中,第一个add
函数接收一个右值引用参数T&& arg
,并输出该参数的值。这个函数作为递归的终止条件,用于处理参数包中只有一个参数的情况。
然后,第二个add
函数是一个递归函数模板,它接收一个右值引用参数T&& arg
和一个参数包Args&&... args
。它首先输出当前参数的值,然后使用递归调用add(std::forward<Args>(args)...)
,将剩余的参数包传递给下一次递归调用。
通过递归调用,这个函数模板可以依次处理参数包中的每个参数,直到参数包为空。这样可以实现对参数包中所有参数的输出。
因此,第一个add
函数是为了处理参数包中只有一个参数的情况,而第二个add
函数则用于递归地处理参数包中的多个参数。
当你add只给一个参数的时候就会调最上面那个add
add(12);
模板元博大精深,后面这块只是对我之前写的一些博客的补充