ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Lesson3

2022-01-18 21:34:20  阅读:172  来源: 互联网

标签:Lesson3 std string auto tcp operator shared


New 和 delete

关键字 new(new operator)做了两件事情:

  • 调用 operator new() 函数为对象分配内存;
  • 调用 placement new() 函数在分配的内存上构造对象(调用对象的构造函数)。

关键字 delete(delete operator)做了两件事情:

  • 调用对象的析构函数;
  • 调用 operator delete() 函数释放对象占用的内存。
class A {
public:
	A() = default;
	~A() = default;
};

int main() {

	auto area = operator new(sizeof A);	// 分配内存
	auto p = new(area) A{};				// 构造

	p->~A();				// 析构
	operator delete(area);	// 释放内存
}

智能指针

回顾一下 make_human() 函数:

// 返回一个指向 Human 对象的指针
Human* make_human(Type type, const std::string& name) {

	if (type == Type::student) {
		return new Student{ name };
	}
	else {	// type == Type::teacher
		return new Student{ name };
	}
}

我们在函数内部使用 new operator 创建了 Human 对象(分配了内存)。如果对象没有销毁(内存没有释放),就会造成内存泄露。函数调用者得到了指向这块内存的指针,所以释放内存的责任就落在了函数调用者身上。如果函数调用层数很多,或者对象的创建和销毁距离很远,那么用户很可能忘记释放。

解决的方法是使用 RAII(Resource Acquisition Is Initialization,资源获取即初始化),让对象自己管理自己的内存。定义一个资源管理类,在构造的时候获取资源,在析构的时候释放资源。资源管理类定义在栈上,所以它的析构函数一般会自己调用,不需要用户调用。

class HumanPtr {
public:
	HumanPtr(Human* p) : p_{ p } {}
	~HumanPtr() { delete p_; }

private:
	Human* p_;
};

// 返回一个内部管理 Human 对象的 HumanPtr 对象
HumanPtr make_human(Type type, const std::string& name) {

	if (type == Type::student) {
		return new Student{ name };
	}
	else {	// type == Type::teacher
		return new Teacher{ name };
	}
}

int main() {

	auto p = make_human(Type::student, "mimi");
}	// 对象 p 被释放,其管理的 Human 对象也被释放

C++ 提供了智能指针,帮助我们管理资源:

  • Unique_ptr:资源只能被一个地方持有,不能复制,只能移动。
  • Shared_ptr:资源可以被多个地方持有,可以复制和移动。
  • Weak_ptr:不持有资源,用来解决 shared_ptr 循环引用的问题。

一般使用 make_unique() 和 make_shared() 函数创建智能指针,下面两种写法是一样的:

auto p1 = std::unique_ptr<Student>{ new Student{"mimi"} };
auto p2 = std::make_unique<Student>("mimi");

注意,下面两种写法是不一样的:

auto p3 = std::shared_ptr<Student>{ new Student{"mimi"} };
auto p4 = std::make_shared<Student>("mimi");
  • p3 分配了 2 次内存,先分配 Student 的内存,再分配 shared_ptr 引用计数的内存;
  • p4 分配了 1 次内存,直接一起分配 Student 和 shared_ptr 引用计数的内存。

在一些情况下,我们需要从对象返回一个指向自己的 shared_ptr。可以让类继承 enable_shared_from_this 类,在需要返回 shared_ptr 时使用 shared_from_this() 函数。注意,该类本身只能通过 shared_ptr 访问,外部不能直接访问构造函数,需要通过该类提供的 create() 函数获得 shared_ptr。有两种方法让构造函数对外部不可见:

  • 将构造函数定义为 private,这样做的缺点是无法使用 make_shared() 函数;
  • 在内部定义 private 的 token 类,并作为参数传入构造函数。比如,一个简单的时间服务器:
#include <iostream>
#include <string>
#include <memory>
#include <boost/asio.hpp>
#include <absl/time/time.h>
#include <absl/time/clock.h>

using boost::asio::ip::tcp;

auto make_daytime_string() {
	return absl::FormatTime(absl::Now(), absl::LocalTimeZone());
}

class tcp_connection
	: public std::enable_shared_from_this<tcp_connection> {
	class token {
		token() = default;
		friend tcp_connection;
	};

public:
	// 外部无法访问私有的 token,也就无法调用构造函数
	tcp_connection(tcp::socket socket, token)
		: socket_{ std::move(socket) } {}

	// 外部只能使用 create() 创建 tcp_connection 对象
	static auto create(tcp::socket socket) {
		return std::make_shared<tcp_connection>(std::move(socket), token{});
	}

	void start() {
		message_ = make_daytime_string();
		boost::asio::async_write(
			socket_,
			boost::asio::buffer(message_),
			[self = shared_from_this()](boost::system::error_code, std::size_t) {}
		);
	}

private:
	tcp::socket socket_;
	std::string message_;
};

class tcp_serve {
public:
	tcp_server(boost::asio::io_context& io_context)
		: acceptor_{ io_context, tcp::endpoint{ tcp::v4(), 13 } } {
		start();
	}

private:
	void start() {
		acceptor_.async_accept(
			[this](boost::system::error_code ec, tcp::socket socket) {
				if (!ec) {
					tcp_connection::create(std::move(socket))->start();
				}
				start();
			}
		);
	}

	tcp::acceptor acceptor_;
};

int main( {
	try {
		boost::asio::io_context io_context;
		tcp_server server{ io_context };
		io_context.run();
	}
	catch (std::exception& e) {
		std::cerr << e.what() << '\n';
	}
}

Lamda 表达式

仿函数是可以像函数一样被调用的对象,它重载了 operator() 函数:

class Add {
public:
	template <typename T>
	T operator()(T a, T b) { return a + b; }
};

int main() {

	auto add = Add{};		// 定义 Add 对象
	auto sum = add(1, 2);	// 调用 add 的 operator() 函数
}

仿函数可以作为参数传递给 STL 的算法:

class Cmp {
public:
	template <typename T>
	T operator()(T a, T b) { return a > b; }
}

int main() {

	std::vector<int> nums{ 2,0,2,2 };
	std::sort(nums.begin(), nums.end(), Cmp{});
}

每次使用仿函数都要定义一个类,其实编译器可以帮我们完成,使用 lambda 表达式即可:

int main() {

	auto add = [](auto a, auto b) { return a + b; };	// 定义闭包
	auto sum = add(1, 2);	// 调用闭包的 operator() 函数
}

我们使用 lambda 表达式时,编译器会定义一个闭包类,然后定义相应的闭包对象:

class Add {
public:
	template <typename T1, typename T2>
	auto operator()(T1 a, T2 b) const { return a + b; }
};

int main() {

	auto add = Add{};
	auto sum = add(1, 2);
}

Lambda 表达式可以捕获外部的变量,有 3 种形式:

  • 值捕获:做一份外部变量的拷贝放在闭包中,生命期是由闭包管理的。
  • 引用捕获:在闭包中引用外部变量,生命期是由外部管理的。注意,要确保在闭包被调用时,外部变量还没有被销毁。
  • 移动捕获:将外部变量移动到闭包中,生命期是由闭包管理的。
int main() {

	std::string s{ "abc" };

	auto func1 = [s] {};	// 值
	auto func2 = [&s] {};	// 引用
	auto func3 = [s = std::move(s)] {};	// 移动
}

编译器会定义一个闭包类,然后定义相应的闭包对象:

class Func1 {
public:
	Func1(const std::string& s) : s_(s) {}
	void operator()() const {}
private:
	std::string s_;
};

class Func2 {
public:
	Func2(std::string& s) : s_(s) {}
	void operator()() {}
private:
	std::string& s_;
};

class Func3 {
public:
	Func3(std::string&& s) : s_(s) {}
	void operator()() {}
private:
	std::string s_;
};

int main() {

	std::string s{ "abc" };

	auto func1 = Func1{ s };	// 值
	auto func2 = Func2{ s };	// 引用
	auto func3 = Func3{ std::move(s) };	// 移动
}

标签:Lesson3,std,string,auto,tcp,operator,shared
来源: https://blog.csdn.net/m0_37957950/article/details/122534214

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有