第7章 语言
7.3.7 转移行为
转移构造,转移构造的目的在于独占(霸占)源对象中指针成员所指向的内存,所以它要求源对象(例中的other)“签字画押”,白纸黑字地表明“是我自愿放弃我的堆成员所占的内存,并将其所有权转移给新对象,我自愿让我的堆成员回归为空指向”。
#include <iostream>
using namespace std;
struct MyPtr
{
explicit MyPtr(int value)
: ptr(new int(value))
{
cout << "调用了" << __func__ << "的入参构造"
<< ",入参是:" << value << endl;
}
~MyPtr()
{
delete ptr;
}
//禁止复制构造和复制赋值,复制构造和复制赋值,需要保持源对象不变,所以参数需要使用const修饰
MyPtr(MyPtr const& other) = delete; //禁止复制构造
MyPtr& operator = (MyPtr const& other) = delete; //禁止复制赋值
//转移构造,转移构造的目的在于独占(霸占)源对象中指针成员所指向的内存,所以它要求源对象(例中的
//other)“签字画押”,白纸黑字地表明“是我自愿放弃我的堆成员所占的内存,并将其所有权转移给新对象,
//我自愿让我的堆成员回归为空指向”。
MyPtr(MyPtr&& other) //参数没有const修饰,&&是右值引用
: ptr(other.ptr)
{
cout << "调用了" << __func__ << "的转移构造" << endl;
other.ptr = nullptr;
}
//转移赋值
MyPtr& operator = (MyPtr&& other) //参数没有const修饰,&&是右值引用
{
cout << "调用了" << __func__ << "的转移赋值" << endl;
if(ptr != other.ptr)
{
ptr = other.ptr;
other.ptr = nullptr;
}
return * this;
}
//友元,重写 << 符,用于输出
friend ostream& operator << (ostream& os, MyPtr& mp)
{
os << "得到的MyPtr对象的ptr指向:" << mp.ptr
<< " 值为:" << *mp.ptr << endl;
return os;
}
int * ptr;
};
int main()
{
MyPtr mip(5);
cout << mip;
cout << "------------1------------" << endl;
MyPtr mip_2(10);
cout << mip_2;
cout << "---------转移之后----------" << endl;
//使用转移构造,创建mip_3对象,mip_3将抢夺mip的ptr成员
//将mip强制类型转换为MyPtr&&类型,下面的代码才会是转移构造
MyPtr mip_3(static_cast<MyPtr&&>(mip));
cout << mip_3;
//再查看mip的ptr成员是否变为了空
cout << (mip.ptr == nullptr) << endl;//输出为1,mip.ptr已为空
//此时不能直接访问mip,
//为了方便输入,C++标准库提供了一个特定的move函数,用于代替"static_cast"转换
//我们使用std::move()函数来抢夺mip_2
cout << "使用std::move()函数来完成转移" << endl;
MyPtr mip_4(std::move(mip_2));
cout << mip_4;
cout << (mip_2.ptr == nullptr) << endl;
return 0;
}
运行结果为:
7.5.4 右值引用
#include <iostream>
using namespace std;
void test1()
{
int a = 100;
int b = 50;
int c = a + b; //编译通过,a+b的和会先存储到一个临时对象_T_,然后再存储到c
}
void test2()
{
int a = 100;
int b = 50;
// int& c = a + b; //编译不通过,如果通过,c处境危险。因为c和临时对象_T_是一体了,但是本体_T_很快就会消亡。
//C++的让步的底线是:允许有名字的常量易用附体到无名的临时数据。
int const& c = a + b; //引用c的存在将为存储“a+b”结果的临时变量_T_延续生命。
}
void foo(int& i) //入参是非常量引用,所以调用时不能传递表达式
{
}
void foo_c(int const& i)//入参是常量引用,它可以附体到无名的临时数据,并有效延续对方的生命周期。
{
}
void test3()
{
int a = 100, b = 50;
// foo(a + b); //ERROR! "a + b"计算结果天生是常量
foo_c(a + b); //foo_c的入参是常量引用,可以附体到无名的临时数据,并有效延续对方的生命周期。
}
int main()
{
cout << "Hello world!" << endl;
return 0;
}
#include <iostream>
using namespace std;
void test1()
{
int a = 100, b = 50;
int&& c = a + b; //c是右值引用
cout << "c =" << c << endl; //150
cout << "a + b = " << a + b << endl;
c = 99; //可以修改
cout << "c = " << c << endl;
cout << "a + b = " << a + b << endl;
}
int foo()
{
return 0;
}
void test2()
{
int a, c, c1, d;
//右值引用,就是专门用于附体到某个右值的引用。什么叫右值,粗浅地说就是赋值操作符“=”右边的值。
a = 10; //10此时是右值
c = c1 + 2; //“c1 + 2”的结果此时是右值
d = foo(); //“foo()”返回的结果此时是右值
/*在函数调用时,编译器会产生某些无名的临时数据,这些数据也经常是赋值操作中的右值。
而右值引用主要就是为这些匆匆产生又匆匆消亡的数据“续命”;更现实和残酷一点,是“夺命”或“鹊巢鸠占”,
中性叫法是“转移(move)”。正是因为夺命,所以右值引用只能用在那些“将死”的右值上,不能用在左值身上,
所谓左值也不一定就是赋值操作符左边的值,而是值那些独立的,有名有姓的(有变量或常量名),生命周期还
挺长的家伙,为什么,因为人家获得好好的,不让“夺”*/
int abc = 9;
int&& rr1 = abc * 2; //OK “abc*2”的计算结果存储于某个无名,临时(短命)的右值上,rr1成功从该右值上抢到命,
//int&& rr2 = abc; //ERROR rr2却失败了,因为abc的生命周期还在(活得好好的),不允许这样被转移到rr2身上去。
}
int main()
{
test1();
return 0;
}
第13章 网络
13.6.3 实现多样化回调
1.使用自由函数或静态方法转接
这是一个带有进度显示的下载程序,其中进度显示函数,使用的是类DownloadProgress的静态成员函数 static int down_progress(void* userdata
, curl_off_t dltotal, curl_off_t dlnow
, curl_off_t ultotal, curl_off_t ulnow)
#include <curl/curl.h>
#include <iostream>
#include <fstream>
#include <sstream> //stringstream
#include <cassert>
using namespace std;
int to_size(char* data, size_t size, size_t nmemb, void* userdata)
{
int result_code = 0;
string s(data, size*nmemb);
stringstream ss(s);
ss >> result_code;
if(!ss.bad() && result_code == 213)
{
int* pcode = static_cast <int*> (userdata);
ss >> *pcode;
}
return nmemb*size;
}
int to_stream(char* data, size_t size, size_t nmemb, void* userdata)
{
ostream& os = *static_cast <ostream*> (userdata);
std::string line(data, size*nmemb);
os << line;
return size*nmemb;
}
//取FTP服务器指定文件的大小
int get_server_file_size(string const& server_url
, string const& username
, string const& password
, string const& pathfile)
{
CURL* handle = curl_easy_init();
curl_easy_setopt(handle, CURLOPT_URL, server_url.c_str());
//username和password也需要C形式的字符串
curl_easy_setopt(handle, CURLOPT_USERNAME, username.c_str());
curl_easy_setopt(handle, CURLOPT_PASSWORD, password.c_str());
string cmd = "SIZE " + pathfile; //SIZE后面要有个分号
curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, cmd.c_str());
int filesize = 0;
curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, to_size);
curl_easy_setopt(handle, CURLOPT_HEADERDATA, static_cast <void*> (&filesize));
curl_easy_perform(handle);
cout << "filesize = " << filesize << endl;
curl_easy_cleanup(handle);
return filesize;
}
class DownloadProgress //用于显示进度的类
{
public:
DownloadProgress() = default;
//当需要通知进度时,回调
//将down_progress()移入DownloadProgress类,变身静态成员函数
static int down_progress(void* userdata
, curl_off_t dltotal, curl_off_t dlnow
, curl_off_t ultotal, curl_off_t ulnow)
{
assert(userdata);
DownloadProgress * dp = static_cast <DownloadProgress *> (userdata);
if(! dp->IsStarted())
dp->Start(dltotal);
dp->OnProgress(dlnow);
return 0;
}
bool IsStarted() const {return _total_bytes != 0;}
void Start(size_t total_bytes)
{
_last_progress = 0;
_total_bytes = total_bytes;
_beg = time(nullptr);
}
int OnProgress(size_t downloaded)
{
if(0 == _total_bytes)
return 0;
time_t now = time(nullptr);
double finished_ratio = (1.0 * downloaded) / _total_bytes;
int current_progress = static_cast <int> (50 * finished_ratio); //表示进度的等号的数量
if(_last_progress != current_progress)
{
cout << static_cast <int> (finished_ratio * 100) << "%";
for(int i = 0; i < current_progress; ++i)
cout << '=';
double seconds = now - _beg;
if(seconds >= 1)
{
double speed = downloaded/seconds/1000;
cout << "->" << speed << "K/S";
}
cout << '\n';
_last_progress = current_progress;
}
return 0;
}
private:
time_t _beg;
size_t _total_bytes = 0;
int _last_progress;
};
int main()
{
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL* handle = curl_easy_init();
ofstream ofs("a.zip", ios_base::out | ios_base::binary);
if(!ofs)
{
cerr << "无法打开本地文件a.zip。" << endl;
return -1;
}
string server_url = "ftp://127.0.0.1:21/";
string pathfile = "fengjie/meili/2.zip";
string username = "d2school";
string password = "123456";
//取服务端指定文件大小
size_t file_size = get_server_file_size(server_url, username, password, pathfile);
//告诉libcurl待下载文件的总大小
curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, static_cast <curl_off_t> (file_size));
//开启进度通知
curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L);
// curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, down_progress);
curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, DownloadProgress::down_progress);
DownloadProgress dp;
curl_easy_setopt(handle, CURLOPT_XFERINFODATA, static_cast <void *> (&dp));
//设置如何处理下载的数据
string url = server_url + pathfile;
curl_easy_setopt(handle, CURLOPT_URL, url.c_str());
// //username和password也需要C形式的字符串
curl_easy_setopt(handle, CURLOPT_USERNAME, username.c_str());
curl_easy_setopt(handle, CURLOPT_PASSWORD, password.c_str());
//本次下载采用直接定位到文件的方式,类似于http协议的下载,不需要使用ftp命令: RETR 文件名
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, to_stream);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, static_cast <void*> (&ofs));
curl_easy_perform(handle);//启动下载
ofs.close();//流要关闭
curl_easy_cleanup(handle);
curl_global_cleanup();
return 0;
}
2.用模板实现更强大的回调
绑定Lambda表达式
下面是《白话C++练武篇》书中658页,访问ftp服务器得到报头数据的程序
#include <curl/curl.h>
#include <iostream>
using namespace std;
size_t to_stream(char* data, size_t size, size_t nmemb, void* userdata)
{
ostream& p = * static_cast <ostream *> (userdata);
//从一个C风格的字符串构造字符串,但最多取n个字符或遇到'\0'
std::string line(data, size*nmemb);
//将字符串写入到指定的ostream对象中
p << line;
return size*nmemb;
}
int main()
{
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL* handle = curl_easy_init();
string url = "ftp://d2school:123456@127.0.0.1/:21";
curl_easy_setopt(handle, CURLOPT_URL, url.c_str());
/*在libcurl库中,CURLOPT_HEADERFUNCTION是一个选项,它允许你设置一个回调函数来处理接收到的HTTP头信息。*/
curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, to_stream);
/*libcurl常数CURLOPT_HEADERDATA是libcurl的一个选项,它用于传递指针给libcurl*/
curl_easy_setopt(handle, CURLOPT_HEADERDATA, static_cast <void*> (&cout));
curl_easy_perform(handle);
curl_easy_cleanup(handle);
curl_global_cleanup();
return 0;
}
26行,使用to_stream函数,被设置为访问报头数据,需要的回调函数。它将访问得到的网络数据打印出来。
29行,为了将报头数据打印到控制台,将cout传递给了to_stream, 因为仅仅to_stream是不够的,还需要cout的辅助。
运行结果为。
下面我们使用模板技术实现更强大的回调方式
#include <curl/curl.h>
#include <iostream>
#include <fstream>
#include <string>
#include <functional>
using namespace std;
struct CURLFunctionHelper
{
CURLFunctionHelper(CURL* curl, CURLoption opt_function, CURLoption opt_data)
{
curl_easy_setopt(curl, opt_function, inner_function);
curl_easy_setopt(curl, opt_data, this);
}
template <typename Fn>//使用模板技术
void Bind(Fn&& fn) //fn的类型是个右值
{
//对象fn并非简单复制给了_callback,而是转移给了_callback,即:若是fn含有指针
//类型的成员,则指针复制给_callback, fn的指针成员被赋值为nullptr
_callback = std::move(fn);
}
private:
//简化function类型表达
typedef std::function <size_t(char*, size_t, size_t)> CURLCallback;
//负责转接的静态成员函数
static size_t inner_function(char* data
, size_t size, size_t nmemb
, void* userdata)
{
auto self = static_cast <CURLFunctionHelper*> (userdata);
return self->_callback(data, size, nmemb); //在此处转发
}
//function<>成员
CURLCallback _callback;
};
int main()
{
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL* handle = curl_easy_init();
string url = "ftp://d2school:123456@127.0.0.1/:21";
curl_easy_setopt(handle, CURLOPT_URL, url.c_str());
//构造一个CURLFunctionHelper对象,指明配置项和HEADER相关,在构造函数中,完成handle的设置
CURLFunctionHelper helper(handle, CURLOPT_HEADERFUNCTION, CURLOPT_HEADERDATA);
//绑定一个lambda表达式,注意它的入参和返回值
helper.Bind([](char* data, size_t size, size_t nmemb)->size_t
{
std::string line(data, size*nmemb);
cout << line;
return size*nmemb;
});
curl_easy_perform(handle);
curl_easy_cleanup(handle);
curl_global_cleanup();
return 0;
}
50行,声明一个helper对象,该对象的构造函数中,完成了回调函数的设置
其中,13行,将静态成员函数inner_function设置为回调函数,而inner_function中需要访问非静态成员_call_back, 这需要一个CURLFunctionHelper对象,所以14行,将this对象传递给了inner_function。
53行,调用helper的Bind() 成员函数,Bind()函数,通过转移的方式,将Lambda表达式
[](char* data, size_t size, size_t nmemb)->size_t
{
std::string line(data, size*nmemb);
cout << line;
return size*nmemb;
}
赋给了_callback成员。
实际执行过程,将执行30行的静态成员函数inner_function,而inner_function中,self是14行传递过来的this,35行,调用_callback, 而_callback就是Bind函数转移过来的Lambda表达式,从而实现了对ftp报头数据的打印。
运行效果如下图:
绑定函数对象
#include <curl/curl.h>
#include <iostream>
#include <fstream>
#include <string>
#include <functional>
using namespace std;
struct CURLFunctionHelper
{
CURLFunctionHelper(CURL* curl, CURLoption opt_function, CURLoption opt_data)
{
curl_easy_setopt(curl, opt_function, inner_function);
curl_easy_setopt(curl, opt_data, this);
}
template <typename Fn>
void Bind(Fn&& fn) //参数类型为右值
{
_callback = std::move(fn);//转移
}
private:
//简化function类型表达
typedef std::function <size_t(char*, size_t, size_t)> CURLCallback;
//负责转接的静态成员函数
static size_t inner_function(char* data
, size_t size, size_t nmemb
, void* userdata)
{
auto self = static_cast <CURLFunctionHelper*> (userdata);
return self->_callback(data, size, nmemb); //在此处转发
}
//function<>成员
CURLCallback _callback;
};
struct StdOutputer
{
size_t operator()(char* data, size_t size, size_t nmemb)
{
std::string line(data, size*nmemb);
cout << line ;
return size*nmemb;
}
};
int main()
{
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL* handle = curl_easy_init();
string url = "ftp://d2school:123456@127.0.0.1/:21";
curl_easy_setopt(handle, CURLOPT_URL, url.c_str());
CURLFunctionHelper helper(handle, CURLOPT_HEADERFUNCTION, CURLOPT_HEADERDATA);
//绑定一个函数对象,注意它的入参和返回值
StdOutputer outputer;
helper.Bind(outputer);
curl_easy_perform(handle);
curl_easy_cleanup(handle);
curl_global_cleanup();
return 0;
}
绑定非静态成员函数
#include <curl/curl.h>
#include <iostream>
#include <fstream>
#include <string>
#include <functional>
using namespace std;
struct CURLFunctionHelper
{
CURLFunctionHelper(CURL* curl
, CURLoption opt_function, CURLoption opt_data)
{
curl_easy_setopt(curl, opt_function, inner_function);
curl_easy_setopt(curl, opt_data, this);
}
template <typename Fn>
void Bind(Fn&& fn)
{
_callback = std::move(fn);
}
template <typename Fn, typename C>
void BindMember(Fn&& fn, C* c)
{
_callback = MakeMemberCallBack(fn, c);
}
private:
typedef std::function <size_t(char* , size_t, size_t)> CURLCallback;
template <typename Fn, typename C>
CURLCallback MakeMemberCallBack(Fn&& fn, C* c)
{
return std::bind(C::on_write, c, std::placeholders::_1
, std::placeholders::_2
, std::placeholders::_3);
}
static size_t inner_function(char* data, size_t size
, size_t nmemb, void* userdata)
{
auto self = static_cast <CURLFunctionHelper*> (userdata);
return self->_callback(data, size, nmemb);
}
CURLCallback _callback;
};
struct StdOutputer
{
size_t on_write(char* data, size_t size, size_t nmemb)
{
std::string line(data, size*nmemb);
cout << line;
return size*nmemb;
}
};
int main()
{
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL* handle = curl_easy_init();
string url = "ftp://d2school:123456@127.0.0.1/:21";
curl_easy_setopt(handle, CURLOPT_URL, url.c_str());
CURLFunctionHelper helper(handle, CURLOPT_HEADERFUNCTION, CURLOPT_HEADERDATA);
//绑定一个函数对象,注意它的入参和返回值
StdOutputer outputer;
helper.Bind(std::bind(StdOutputer::on_write
, &outputer//this指针
, std::placeholders::_1, std::placeholders::_2
, std::placeholders::_3));
curl_easy_perform(handle);
curl_easy_cleanup(handle);
curl_global_cleanup();
return 0;
}