muduo C++网络库的学习笔记
文章目录
- 10.3.C++链接linking
- 10.3.2 inline函数
- 10.3.3 模板
- 10.3.4 虚函数
- 10.4 工程项目中头文件的使用规则
- 10.4.2 头文件的使用规则
- 11.1 朴实的C++设计
- 11.2 程序库的二进制兼容性
- 11.3 避免使用虚函数作为库的接口
- 11.4 动态库接口的推荐做法
- 11.5 std::function和std::bind取代虚函数
- 11.5.1基本用途
- 11.5.2 对程序库的影响
- 11.5.5 对面向对象程序设计的影响
- 11.6 iostream的用途与局限
- 11.6.1 stdio格式化输入输出的缺点
- 11.6.2 iostream设计初衷
- 11.6.3 iostream与标准库其他组件的交互
- 11.6.4 iostream在使用方面的缺点
- 11.6.5 iostream在设计方面的缺点
- 11.6.6 一个300行的memory buffer output stream
- 11.6.7 现实中的C++程序如果做文件IO
- 11.7 值语义与数据抽象
- 11.7.1 什么是值语义
- 11.7.2 值语义与生命期
- 11.7.3 值语义与标准库
- 11.7.4 值语义与C++语言
- 11.7.5 什么是数据抽象
- 11.7.6 数据抽象所需的语言措施
- 11.7.7 数据抽象的例子
- 12 C++经验谈
- 12.1 用异或来交换变量是错误的
- 12.2 不要重载全局::operator new()
- 12.2.1 内存管理的基本要求
- 12.2.2 重载::operator new()的理由
- 12.2.3 ::operator new()的两种重载方式
- 12.2.4 现实的开发环境
- 12.2.5 重载::operator new()的困境
- 12.2.6 解决办法:替换malloc
- 12.2.7 为单独的class重载::operator new()有问题吗?
- 12.2.8 有必要自行定制内存分配器吗?
- 12.3 带符号整数的除法与余数
- 12.4 在单元测试中mock系统调用
- 12.4.1 系统函数的依赖注入
- 12.4.2 链接器垫片link seam
- 12.5 慎用匿名namespace
- 12.5.1 C语言的static关键字的两种用法
- 12.5.2 C++语言的static关键字的四种用法
- 12.5.3 匿名namespace的不利之处
- 12.6 采用有利于版本管理的代码格式
- 12.6.1 对diff友好的代码格式
- 12.6.2 对grep友好的代码风格
- 12.6.3 一切为了效率
- 12.7 再探std::string
- 12.7.1 直接拷贝eager copy
- 12.7.2 写时赋值copy on write
- 12.7.3 短字符串优化
- 12.8 用STL algorithm轻松解决算法面试题
10.3.C++链接linking
10.3.2 inline函数
inline关键字在源文件中不是必须的,编译器可以自动判断;
inline在头文件中还是需要的,可以防止链接器重复定义(multipe definition);
然后判断一个C++可执行文件是debug build还是release build?即判断:一个可执行文件是-O0编译的还是-O2编译的?
- eg:
#include <cstdio>
#include <cmath>
#include "printer.h"
#include <vector>int main()
{std::vector<int> vi;printf("%zd\n", vi.size());return 0;
}
my_course/course/12/01_math/01/run.sh
#!/bin/bash
set -erm -rf build
cmake -B build
cmake --build build
my_course/course/12/01_math/01/CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(hellocmake)add_executable(main main.cpp)
- 测试:
debug
wangji@DESKTOP-QNG23J0:~/test/test/my_course/course/12/01_math/01$ g++ -Wall main.cpp
wangji@DESKTOP-QNG23J0:~/test/test/my_course/course/12/01_math/01$ nm ./a.out |grep size|c++filt
000000000000142c W std::vector<int, std::allocator<int> >::size() const
release
wangji@DESKTOP-QNG23J0:~/test/test/my_course/course/12/01_math/01$ g++ -Wall -O2 main.cpp
wangji@DESKTOP-QNG23J0:~/test/test/my_course/course/12/01_math/01$ nm ./a.out |grep size|c++filt
10.3.3 模板
- my_course/course/12/01_math/01/Request.h
class PrintRequest
{int m_user_d_;public:int getUserId() const { return m_user_d_; }
};class ScanRequest
{int m_userId_;public:int getUserId() const { return m_userId_; }
};
- my_course/course/12/01_math/01/Printer.h
#include "Request.h"class PrintRequest;
class ScanRequest;class Printer
{// 模板函数解析request公共部分// 实现放在源文件中,好处:Printer的用户看不到decodeRequest函数模板的定义,也可以加快编译速度template <typename REQ>void decodeRequest(const REQ &req);void processRequest();int m_currentRequestUserId_;public:void onRequest(const PrintRequest &req);void onRequest(const ScanRequest &req);
};
- my_course/course/12/01_math/01/Printer.cc
#include "Printer.h"
#include "Request.h"// 现在编译器能给看到decodeRequest的定义,也就能给具体化
template <typename REQ>
void Printer::decodeRequest(const REQ &req)
{m_currentRequestUserId_ = req.getUserId();// decode other parts
}void Printer::onRequest(const PrintRequest &req)
{decodeRequest(req);processRequest();
}
void Printer::onRequest(const ScanRequest &req)
{decodeRequest(req);processRequest();
}
C++11 extern template特性
- 阻止隐式模板具体化,使得std::string和std::
#include <cstdio>
#include <cmath>
// #include "printer.h"
#include <vector>
#include <string>
#include <iostream>using namespace std;int main()
{// std::vector<int> vi;// printf("%zd\n", vi.size());string name;cin >> name;cout << "hello, " << name << endl;return 0;
}
目标文件,并没有具体化iostream和string的两个大模板
wangji@DESKTOP-QNG23J0:~/test/test/my_course/course/12/01_math/01$ nm build/main |grep " [TW]"
0000000000001410 T __libc_csu_fini
00000000000013a0 T __libc_csu_init
0000000000001418 T _fini
0000000000001180 T _start
0000000000004000 W data_start
0000000000001269 T main
wangji@DESKTOP-QNG23J0:~/test/test/my_course/course/12/01_math/01$ man nm而是引用了标准库中的实现
wangji@DESKTOP-QNG23J0:~/test/test/my_course/course/12/01_math/01$ nm build/main |grep -o " U .*"U _Unwind_Resume@@GCC_3.0 是string的构造函数与析构函数U _ZNSolsEPFRSoS_E@@GLIBCXX_3.4U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1Ev@@GLIBCXX_3.4.21U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED1Ev@@GLIBCXX_3.4.21U _ZNSt8ios_base4InitC1Ev@@GLIBCXX_3.4U _ZNSt8ios_base4InitD1Ev@@GLIBCXX_3.4U _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@@GLIBCXX_3.4U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@@GLIBCXX_3.4U _ZStlsIcSt11char_traitsIcESaIcEERSt13basic_ostreamIT_T0_ES7_RKNSt7__cxx1112basic_stringIS4_S5_T1_EE@@GLIBCXX_3.4.21U _ZStrsIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RNSt7__cxx1112basic_stringIS4_S5_T1_EE@@GLIBCXX_3.4.21U __cxa_atexit@@GLIBC_2.2.5U __gxx_personality_v0@@CXXABI_1.3U __libc_start_main@@GLIBC_2.2.5U __stack_chk_fail@@GLIBC_2.4
10.3.4 虚函数
每个多态class都有一份vtable
- 定义或者继承了虚函数的对象中会有一个隐含成员:指向vtable的指针vptr
- 在构造和析构对象的适合,编译器生成的代码会修改这个vptr成员,会用到vtable的定义(使用其他地址)
- eg:
#include <cstdio>
#include <cmath>
// #include "printer.h"
#include <vector>
#include <string>
#include <iostream>using namespace std;class Base
{public:virtual ~Base();virtual void doIt();
};int main()
{Base *b = new Base();b->doIt();return 0;
}
- 测试:出现这种错误的根本原因是:程序中某个虚函数没有定义。虽然报错显示:找不到虚函数表的定义
[ 50%] Building CXX object CMakeFiles/main.dir/main.cpp.o
[100%] Linking CXX executable main
/usr/bin/ld: CMakeFiles/main.dir/main.cpp.o: in function `Base::Base()':
main.cpp:(.text._ZN4BaseC2Ev[_ZN4BaseC5Ev]+0xf): undefined reference to `vtable for Base'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/main.dir/build.make:97: main] Error 1
make[1]: *** [CMakeFiles/Makefile2:83: CMakeFiles/main.dir/all] Error 2
make: *** [Makefile:91: all] Error 2
10.4 工程项目中头文件的使用规则
头文件通常分为:C语言系统头文件,C++标准库头文件,C++第三方库头文件,本公司的基础库头文件,本项目的头文件
10.4.2 头文件的使用规则
- eg:查找头文件包含的小技巧。eg:一个程序只包含了<iostream>,但是却能使用std:string,这个<string>是如何被引入的?
// #include <cstdio>
// #include <cmath>
// #include "printer.h"
// #include <vector>
// #include <string>
#include <iostream>using namespace std;// class Base
// {// public:
// virtual ~Base();
// virtual void doIt();
// };int main()
{// Base *b = new Base();// b->doIt();// std::cout << sizeof(Base) << std::endl;std::string s = "wangji";return 0;
}
wangji@DESKTOP-QNG23J0:~/test/test/my_course/course/12/01_math/01$ cat >string
#wangji wangji
wangji@DESKTOP-QNG23J0:~/test/test/my_course/course/12/01_math/01$ g++ -M -I . main.cpp
In file included from /usr/include/c++/9/bits/locale_classes.h:40,from /usr/include/c++/9/bits/ios_base.h:41,from /usr/include/c++/9/ios:42,from /usr/include/c++/9/ostream:38,from /usr/include/c++/9/iostream:39,from main.cpp:6:
11.1 朴实的C++设计
11.2 程序库的二进制兼容性
11.3 避免使用虚函数作为库的接口
11.4 动态库接口的推荐做法
1.暴露的接口里面不要有虚函数,要显式声明构造函数、析构函数,并且不能inline
- graphics.h
class Graphics
{public:Graphics();~Graphics();void drawLine(int x0, int y0, int x1, int y1);private:class Impl;std::unique_ptr<Impl> impl;
};
2.这部分代码位于so中,随着库的升级一起变化
graphics.cc
#include "graphics.h"class Graphics::Impl
{public:void drawLine(int x0, int y0, int x1, int y1);
};//编译器可以看到Impl的定义,编译通过
Graphics::Graphics() : impl(new Impl) {}//析构函数是空的,也必须放到这里定义。
Graphics::~Graphics() {}void Graphics::drawLine(int x0, int y0, int x1, int y1)
{impl->drawLine(x0, y0, x1, y1);
}
3.增加新的功能,不必通过继承的方式,就可以原地修改,容易保持二进制兼容性
graphics.h
#include <memory>class Graphics
{public:Graphics();~Graphics();void drawLine(int x0, int y0, int x1, int y1);// 新增的非虚函数不影响现有的可执行文件void drawLine(double x0, double y0, double x1, double y1);private:class Impl;std::unique_ptr<Impl> impl;
};
graphics.cc
#include "graphics.h"class Graphics::Impl
{public:void drawLine(int x0, int y0, int x1, int y1);void drawLine(double x0, double y0, double x1, double y1);
};//编译器可以看到Impl的定义,编译通过
Graphics::Graphics() : impl(new Impl) {}//析构函数是空的,也必须放到这里定义。
Graphics::~Graphics() {}void Graphics::drawLine(int x0, int y0, int x1, int y1)
{impl->drawLine(x0, y0, x1, y1);
}void Graphics::drawLine(double x0, double y0, double x1, double y1)
{impl->drawLine(x0, y0, x1, y1);
}
pimpl C语言的库同样可以用,eg:libevent2中的struct event_base
- 为什么非虚函数比虚函数更健壮?
因为虚函数是虚表指针+offset来决定虚函数的,而非虚函数是通过名字找到对应的函数的
11.5 std::function和std::bind取代虚函数
11.5.1基本用途
#include <functional>
#include <string>class Foo
{public:void methodA();void methodInt(int a);void methodString(std::string const &str);
};class Bar
{public:void methodB();
};void main()
{// 无参,无返回值std::function<void()> f1;std::function<void(int)> f2;Foo foo;f1 = std::bind(&Foo::methodA, &foo);f1();Bar bar;f1 = std::bind(&Bar::methodB, std::ref(bar));f1();f1 = std::bind(&Foo ::methodInt, &foo, 42);f1();f1 = std::bind(&Foo::methodString, &foo, "hello");f1(); //调用foo.methodString("hello")// 要留意bind的实参(const char*)的生命期,她不应该短于f1的生命期// 必要时,可通过f1 = std::bind(&Foo::methodString, &foo, "hello”_s);来保证安全f2 = std::bind(&Foo::methodInt, &foo, std::placeholders::_1);f2(53);
}
11.5.2 对程序库的影响
程序库的设计不应该给使用者带来不必要的耦合限制,而继承是第二强的一种耦合,最强的耦合是友元;
常规OO设计:虚函数+派生覆写;
基于std::function的设计:以std::function作为接口
- eg:线程库
#include <functional>
#include <string>// 一个基于std::function的Thread class基本数据结构
class Thread
{public:using ThreadCallback = std::function<void()>;Thread(ThreadCallback const &cb) : cb_(cb) {}void start(){// somae magic to call run() in new created thread}private:ThreadCallback cb_;void run(){cb_();}
};// 使用方式:不需要继承
class Foo
{public:void runInthread();void runInAnotherThread(int);
};void main()
{Foo foo;Thread thread1(std::bind(&Foo::runInthread, &foo));Thread thread2(std::bind(&Foo::runInAnotherThread, &foo, 100));thread1.start();thread2.start();
}
- eg:网络库
#include <functional>
#include <string>// network library
class Connection;
class NetServer
{public:typedef std::function<void(Connection *)> ConnectionCallback;typedef std::function<void(Connection *, const void *, int len)> MessageCallback;NetServer(std::uint16_t port);~NetServer();void registerConnectionCallback(ConnectionCallback const &);void registerMessageCallback(MessageCallback const &);void sendMessage(Connection *, const void *buf, int len);void run();
};// 以std::function作为桥梁
// user code
class EchoService
{public:// 符合NetServer::sendMessage原型using SendMessageCallback = std::function<void(Connection *, const void *buf, int len)>;EchoService(const SendMessageCallback &sendMsgCb) : sendMessageCb_(sendMsgCb) {}// 符合NetServer::ConnectionCallback原型void onConnection(Connection *conn){printf("");}// 符合NetServer::MessageCallback原型void onMessage(Connection *conn, const void *buf, int size){sendMessageCb_(conn, buf, size); // echo back}private:SendMessageCallback sendMessageCb_;
};// 上帝:把各种部件拼接起来
int main()
{NetServer server(7);EchoService echo(std::bind(&NetServer::sendMessage, &server, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));server.registerConnectionCallback(std::bind(&EchoService::onConnection, &echo, std::placeholders::_1));server.registerMessageCallback(std::bind(&EchoService::onMessage, &echo, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));server.run();
}
11.5.5 对面向对象程序设计的影响
用std::function代替虚函数,OO设计模式:行为模式、Factory Method创建型模式、Stratery模式、Command模式、Template Method等都可以不用了
- 以上述EchoService为例,EchoService需要一个函数原型满足SendMessageCallback的东西来发送消息,而并不关心数据发送到网络上还是Mock上
面向对象的接口与实现分离:
先写一个AbstructDataSink interface,包含sendMessage()这个虚函数,然后派生出两个class:NetDataSink和MockDataSink;
EchoService的构造函数应该以AbstructDataSink*为参数
基于对象的接口与实现分离:
直接传入一个SendMessageCallback对象
什么时候使用继承?
OO中的public继承,即为了实现接口与实现分离,muduo只会在派生类的数目和功能完全确定的情况下使用;
- eg:IO multiplexing在不同操作系统下不同有不同的实现方法,数目固定,且功能完全确定。用多态来代替switch-case可以达到简化代码的目的;
11.6 iostream的用途与局限
11.6.1 stdio格式化输入输出的缺点
#include <stdio.h>
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <iostream>int main()
{//缓冲区溢出危险:输入的name没有指定大小// char name[80];// scanf("%s\n", name);// // 安全做法:// constexpr int max_name = 80;// char myname[max_name];// char fmt[10];// sprintf(fmt, "%%%%ds", max_name - 1);// scanf(fmt, name);// int64_t在32bit和64bit平台上是不同的类型int64_t x = 100;printf("%" PRIo64 "\n", x); //输出的是8进制printf("%06" PRIo64 "\n", x);std::cout << std::dec << x << std::endl;// 等价于printf("%""ld""\n",x); // 64bit OSprintf("%""lld""\n",x); // 32bit OS// 等价于// printf("%ld\n", x); // 64bit OS// printf("%lld", x); // 32bit OSstd::size_t i = 1;printf("%zd\n", i);return 0;
}
11.6.2 iostream设计初衷
#include <ostream>
#include <iostream>class Date
{public:Date(int year, int month, int day) : year_(year), month_(month), day_(day) {}void writeTo(std::ostream &out) const{out << year_ << "-" << month_ << "-" << day_;}friend std::ostream &operator<<(std::ostream &out, const Date &date){out << date.year_ << date.month_ << date.day_ << std::endl;}private:int year_;int month_;int day_;
};std::ostream &operator<<(std::ostream &out, const Date &date)
{date.writeTo(out);return out;
}int main()
{Date date{2022, 11, 3};std::cout << date << std::endl;return 0;
}
11.6.3 iostream与标准库其他组件的交互
11.6.4 iostream在使用方面的缺点
#include <ostream>
#include <iostream>
#include <iomanip> //操作子格式化需要该头文件
class Date
{public:Date(int year, int month, int day) : year_(year), month_(month), day_(day) {}// 输出2022-11-03// iostream输出格式繁琐void writeTo(std::ostream &out) const{out << year_ << "-"<< std::setw(2) << std::setfill('0') << month_ << "-"<< std::setw(2) << std::setfill('0') << day_;}void writeTo(std::ostream &out){out << year_ << "-" << month_ << "-" << day_;char buf[32];snprintf(buf, sizeof(buf), "%d-%02d-%02d", year_, month_, day_);out << buf;}private:int year_;int month_;int day_;
};std::ostream &operator<<(std::ostream &out, const Date &date)
{date.writeTo(out);return out;
}int main()
{Date date{2022, 11, 3};std::cout << date << std::endl;const char *name = "wangji";int age = 30;// 注:数字表示替换字符串中要替换的位置,若一个字符串要替换两个int类型,在替换位置分别 写%1$d和%2$d.printf("My name is %1$s, I am %2$d years old.\n", name, age);std::cout << "My name is " << name << ", I am " << age << " years old." << std::endl;// 将整数转为十六进制,会持续影响ostream的状态int x = 8888;std::cout << std::hex << x << std::endl; // 0x22b8std::cout << 123 << std::endl; // 0x7bdouble d = 123.45;// C风格的格式输出都不会影响,很舒服// 一般情况考虑用snprintf()打印到栈上缓冲,再用ostream输出// stdio函数时线程安全的,iostream不是线程安全的:cout.operator<<(a).operator<<(b)两次调用期间可能会被打断,造成输出不连续// fprintf(stdout, "%s %d", a, b);打印的内容不会受其他线程影响printf("\n%8.3f\n", d);std::cout << d << std::endl;using namespace std;cout << d << endl;// setprecision会影响后续输出的精度,setw则不会cout << setw(8) << fixed << setprecision(3) << d << endl;cout << d << endl;return 0;
}
总结
输入:istream不适合输入带格式的数据;推荐做法:std::getline读入一行数据到std::string,然后用正则表达式判断正误,并作分组,最后用strtod,strtol,或者std::string相关函数做类型转换
输出:ostream仅做简单的无格式输出
不要用ostream来写log
ostringstream会动态分配内存,不适合性能较高的场合
文件IO,如果用作文本文件的输入或者输出,fstream也有上述的缺点
iostream在某些场合比stdio快,某些又慢,对于高性能而言,需自己实现字符串转换
11.6.5 iostream在设计方面的缺点
面向对象中的public继承需要满足Liskov替代原则,就是OO继承强调的是可替代性,派生类的对象可以替换基类对象;
只有真正的is-a关系采用public继承,其他均以组合替代;
11.6.6 一个300行的memory buffer output stream
LogStream接口定义见muduo
其他程序如何使用LogStream作为输出呢?
#include <ostream>
#include <iostream>
#include <iomanip> //操作子格式化需要该头文件
class Date
{public:Date(int year, int month, int day) : year_(year), month_(month), day_(day) {}// 输出2022-11-03// iostream输出格式繁琐void writeTo(std::ostream &out) const{out << year_ << "-"<< std::setw(2) << std::setfill('0') << month_ << "-"<< std::setw(2) << std::setfill('0') << day_;}template <typename OStream>void writeTo(OStream &out){out << year_ << "-" << month_ << "-" << day_;char buf[32];snprintf(buf, sizeof(buf), "%d-%02d-%02d", year_, month_, day_);out << buf;}private:int year_;int month_;int day_;
};template <typename OStream>
OStream& operator<<(OStream& out, const Date &date)
{date.writeTo(out);return out;
}
11.6.7 现实中的C++程序如果做文件IO
在C++项目中,自己写个File class,把项目用到的文件IO功能简单封装一下,通常就能满足需要。
11.7 值语义与数据抽象
11.7.1 什么是值语义
指的是对象的拷贝与原对象无关,就像拷贝int一样,eg:
- C++内置类型bool、int、double、
- 标准库:pair<>、vector<>、map<>、string
对象语义:对象拷贝是禁止的,eg:
- muduo的Thread的拷贝是禁止的,因为Thread代表一个线程,拷贝一个Thread对象并不能让系统增加一个一模一样的线程
11.7.2 值语义与生命期
- eg:若Parent拥有Child,Child的生命期由其Parent控制
#include <memory>
class Parent;class Child
{public:explicit Child(Parent *myParent) : myParent_(myParent) {}private:Parent *myParent_;
};class Parent
{public:Parent() {}private:std::unique_ptr<Child> myChild_;
};
采用std::shared_ptr写法:
#include <memory>
class Parent;
using ParentPtr = std::shared_ptr<Parent>;class Child
{public:explicit Child(const ParentPtr &myParent) : myParent_(myParent) {}private:std::weak_ptr<Parent> myParent_;
};using ChildPtr = std::shared_ptr<Child>;
class Parent : public std::enable_shared_from_this<Parent>
{public:Parent() {}void addChild(){myChild.reset(new Child(shared_from_this()));}private:ChildPtr myChild;
};int main()
{ParentPtr p(new Parent());p->addChild();
}
- eg:Child持有mom和dad的parents,一个parent持有一个或者多个child;mom知道她的配偶spouse,dad知道她的配偶spouse
#include <memory>
#include <vector>
// 如哦不使用智能指针,用C++做面向对象编程会困难重重
class Parent;
using ParentPtr = std::shared_ptr<Parent>;class Child
{public:explicit Child(const ParentPtr &mom, const ParentPtr &dad) : myMom_(mom), myDad_(dad) {}private:std::weak_ptr<Parent> myMom_;std::weak_ptr<Parent> myDad_;
};using ChildPtr = std::shared_ptr<Child>;
class Parent
{public:Parent() {}void addChild(const ChildPtr &child){myChildren.push_back(child);}void setSpouse(const ParentPtr &spouse){mySpouse = spouse;}private:std::vector<ChildPtr> myChildren;std::weak_ptr<Parent> mySpouse;
};int main()
{ParentPtr mom(new Parent());ParentPtr dad(new Parent());mom->setSpouse(dad);dad->setSpouse(mom);{ChildPtr child(new Child(mom, dad));mom->addChild(child);dad->addChild(child);}{ChildPtr child(new Child(mom, dad));mom->addChild(child);dad->addChild(child);}
}
11.7.3 值语义与标准库
C++编译器会为class默认提供copy constructor和assignment operator,所以在写一个C++ class的时候,让他默认继承boost::noncopyable,几乎总是正确的。
11.7.4 值语义与C++语言
C++的设计初衷是让用户定义的类型class能像内置类型int一样工作。
11.7.5 什么是数据抽象
数据抽象data abstraction是与面向对象object oriented并列的一种编程范式。
11.7.6 数据抽象所需的语言措施
11.7.7 数据抽象的例子
12 C++经验谈
12.1 用异或来交换变量是错误的
- eg:将“12345”反转为“54321”
// C
void reverse(char *str, int n)
{char *begin = str;char *end = str + n - 1;while (begin < end){auto tmp = *begin;*begin = *end;*end = tmp;begin++;--end;}
}// C++
void reverse_by_std(char* str, int n)
{std::reverse(str,str+n);
}
12.2 不要重载全局::operator new()
12.2.1 内存管理的基本要求
既不重复delete,也不漏掉delete
12.2.2 重载::operator new()的理由
12.2.3 ::operator new()的两种重载方式
//方式1
#include <new>
void *operator new(size_t size);
void operator delete(void *p);//方式2
void *operator new(size_t size, const char *file, int line);
void operator delete(void *p, const char *file, int line);Foo *p = new (__FILE, __LINE__) Foo;
12.2.4 现实的开发环境
12.2.5 重载::operator new()的困境
12.2.6 解决办法:替换malloc
12.2.7 为单独的class重载::operator new()有问题吗?
12.2.8 有必要自行定制内存分配器吗?
重载::operator new()或许在某些临时的场合能应急,但是不应该作为一种策略来使用。
如果需要,可以从malloc层面入手,彻底替换内存分配器。
12.3 带符号整数的除法与余数
12.4 在单元测试中mock系统调用
12.4.1 系统函数的依赖注入
方法1:
采用传统的面向对象的手法,借助运行期的迟绑定实现注入与替换。自己写一个System interface,把程序里面的open,close,write等函数用虚函数封装一层。
方法2:采用编译器或者链接期的迟绑定,因此程序只会用到一个implementation object,为此虚函数调用的代价有些不值得。(与系统调用相比,虚函数这点开销可以忽略不计)。
- 在一个system namespace头文件,在其中声明read()和write()等普通函数,然后在.cc文件里转发给对应系统函数::read()和::write()。
- 无需用到虚函数,代码写起来也比较简洁,只用前缀sockets::即可。
// real:SocketsOps.h
namespace sockets
{int connect(int port, const struct sockaddr_in &addr);
}// real:SocketsOps.cc
int sockets::connect(int port, const struct sockaddr_in &sockaddr)
{return ::connect(port, sockaddr_cast(&addr), sizeof(addr));
}
// 编译普通程序
g++ main.cc mynetcat.cc SocketsOps.cc - o mynetcat// stub:单元测试,MockSocketsOps.cc
int sockets::connect(int port, const struct sockaddr_in &sockaddr)
{errno = EAGIN;return -1;
}
// 编译单元测试
g++ main.cc mynetcat.cc MockSocketsOps.cc - o mynetcat
12.4.2 链接器垫片link seam
- 一开始没有考虑单元测试,如何注入mock系统调用?
// 在链接时,会优先采用我们自己定义的函数
connect_func_t connect_func = dlsym(RTDL_NEXT, "connect");bool mock_connect;
int mock_connect_errno;
// mock connectextern "C" int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{if (mock_connect){errno = mock_connect_errno;return errno == 0 ? 0 : -1;}else{return connect_func(sockfd, addr, addrlen);}
}
12.5 慎用匿名namespace
一些小的helper函数会放到匿名namespace中
12.5.1 C语言的static关键字的两种用法
函数内的静态变量
- 使用静态变量的函数是不可重入的,也不是线程安全的
函数体之外修饰变量或者函数
- 仅对本文件可见
- 匿名namespace可以达到相同的效果
12.5.2 C++语言的static关键字的四种用法
除了以上C语言的两种用法,有定义了两种新用法:
- 修饰class的数据成员
- 修饰class的成员函数
12.5.3 匿名namespace的不利之处
anon.cc
namespace
{void foo() {}
} // namespaceint main()
{foo();
}
anonlib.cc
namespace
{void foo() {}
} // namespace
上述两个文件都定义了匿名空间中的foo()函数,那么gdb则无法区分这两个函数
- 匿名namespace中的函数是weak text,链接的时候若发生重名,linker不会报错
g++ -g anon.cc anonlib.cc
(gdb) b '
(anonymous namespace)::foo() __cxa_finalize@plt _fini _start anonlib.cc frame_dummy main()
__cxa_finalize __do_global_dtors_aux _init anon.cc deregister_tm_clones main register_tm_clones
解决办法:
- 调试时使用文件名:行号
- 使用具体的namespace名字,Boost中就常用boost::detail来存放不应该暴露给用户,但又不得不放到头文件里面的函数或者class
12.6 采用有利于版本管理的代码格式
C和C++代码中的换行符都被编译器(预处理之后)当做white space来对待。
- eg:等价写法:
foo(1, 2)
foo(1,2)
12.6.1 对diff友好的代码格式
不适用/**/来注释多行代码
12.6.2 对grep友好的代码风格
12.6.3 一切为了效率
12.7 再探std::string
12.7.1 直接拷贝eager copy
12.7.2 写时赋值copy on write
12.7.3 短字符串优化
12.8 用STL algorithm轻松解决算法面试题
生成N个不同元素的全排列
#include <algorithm>
#include <vector>
#include <iostream>
#include <iterator>using namespace std;
int main()
{std::vector<int> vec = {1, 2, 3, 4};int count = 0;do{std::cout << ++count << ": ";std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(std::cout, ","));cout << endl;} while (next_permutation(vec.begin(), vec.end()));return 0;
}
生成从N个元素中取出M个的所有组合
- 对序列{1,1,1,0,0,0,0}做全排列。对于每个排列,输出数字1对应的位置上的元素。
- eg:
#include <algorithm>
#include <vector>
#include <iostream>
#include <iterator>using namespace std;
int main()
{std::vector<int> values = {1, 2, 3, 4, 5, 6, 7};std::vector<int> select = {1, 1, 1, 0, 0, 0, 0};int count = 0;do{std::cout << ++count << ": ";for (auto i = 0; i < select.size(); ++i){if (select[i] == 1){cout << values[i] << std::endl;}}cout << endl;} while (prev_permutation(select.begin(), select.end()));return 0;
}
用unique()去除连续的重复空白
- 给定一个字符串,要求原地把相邻的多个空格替换为一个,例如a__b,输出为a_b。
- 所有针对于STL algorithm都只能调整区间内元素的顺序,不能真正删除容器内的元素
void removeContinousSpaces(std::string &str)
{该函数的作用是“去除”容器或者数组中相邻元素的重复出现的元素,注意 (1) 这里的去除并非真正意义的erase,而是将重复的元素放到容器的末尾,返回值是去重之后的尾地址。 (2) unique针对的是相邻元素,所以对于顺序顺序错乱的数组成员,或者容器成员,需要先进行排序,可以调用std::sort()函数/**/auto last = std::unique(str.end(), str.end(), [](char a, char b){ return a == ' ' && b == ' '; });str.erase(last, str.end());
}
用一台4GiB内存的机器对磁盘上的单个100GB文件排序
- 假设要归并从小到大排序好的32个文件
- 标准思路是:先分块排序,然后多路归并成输出文件。多路归并使用heap排序。
- 用{make,push,pop}_heap实现多路归并
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>typedef int Record;
typedef std::vector<Record> File;struct Input
{Record value;size_t index;const File* file;explicit Input(const File* f): value(-1),index(0),file(f){ }bool next(){if (index < file->size()){ value = (*file)[index];++index;return true;} else {return false;}}bool operator<(const Input& rhs) const{// make_heap to build min-heap, for mergingreturn value > rhs.value;}
};File mergeN(const std::vector<File>& files)
{File output;std::vector<Input> inputs;// 构造二叉堆// 二叉堆非常适合解决在连续的插入和删除操作中,快速定位最大值或最小值的问题//在最大堆中,根节点的值最大; 在最小堆中,根节点的值最小;局部和整体一样for (size_t i = 0; i < files.size(); ++i) {Input input(&files[i]);if (input.next()) {inputs.push_back(input);}}std::make_heap(inputs.begin(), inputs.end());// 循环结束,即堆为空,说明每个文件都读取完毕了while (!inputs.empty()) {// 将堆顶元素放到末尾inputs.back()std::pop_heap(inputs.begin(), inputs.end());output.push_back(inputs.back().value);// 从堆顶元素所属的文件读入下一条记录,成功则放回到堆中if (inputs.back().next()) {std::push_heap(inputs.begin(), inputs.end());} else {inputs.pop_back();}}return output;
}int main()
{// 假设要归并从小到大排序好的32个文件,构造一个32元素的min heap,每次取出堆顶的元素,将其Record写入输出文件const int kFiles = 32;std::vector<File> files(kFiles);for (int i = 0; i < kFiles; ++i) {File file(rand() % 1000);std::generate(file.begin(), file.end(), &rand);std::sort(file.begin(), file.end());files[i].swap(file);}File output = mergeN(files);std::copy(output.begin(), output.end(),std::ostream_iterator<Record>(std::cout, "\n"));
}
muduo C++网络库的学习笔记相关推荐
- 《muduo网络库》学习笔记——muduo学习总结
muduo是基于非阻塞的IO和事件驱动的网络库(Reactor模式),其核心是一个事件循环EventLoop,用于响应计时器和IO事件.muduo采用基于对象(object-based)而非面向对象( ...
- 《Linux多线程服务端编程:使用muduoC++网络库》学习笔记
文章目录 第1章 线程安全的对象生命期管理 1.1 当析构函数遇到多线程 1.1.1 线程安全的定义 1.1.3 线程安全实例 1.2 对象的创建很简单 1.3 销毁很难 1.4 线程安全的Obser ...
- 《muduo网络库》学习笔记——时间轮Timeing wheel
目录 1. 概述 2. Timing wheel 原理 2.1 连接超时被踢掉的过程 2.2 连接刷新 2.3 多个连接 3. 代码实现与分析 1. 概述 本文介绍如何使用 timing wheel ...
- 《Linux多线程服务端编程——使用muduo C++网络库》读书笔记
第一章 线程安全的对象生命期管理 第二章 线程同步精要 第三章 多线程服务器的适用场合与常用编程模型 第四章 C++多线程系统编程精要 1.(P84)11个常用的最基本Pthreads函数: 2个:线 ...
- Lib库使用学习笔记
Lib库使用学习笔记 转自:http://blog.csdn.net/macky0668/article/details/6044867 技术前沿 2008-03-31 14:21:10 阅读177 ...
- 【Python-pywt】 小波变化库—Pywavelets 学习笔记
(转载) [Python ]小波变化库--Pywavelets 学习笔记_nanbei2463776506的博客-CSDN博客 https://blog.csdn.net/nanbei24637765 ...
- 《Linux多线程服务端编程:使用muduo C++网络库》书摘6.6.2节
6.6.2 常见的并发网络服务程序设计方案 W. Richard Stevens 的<UNIX 网络编程(第2 版)>第27 章"Client-ServerDesign Alte ...
- 《C++标准库》学习笔记 — STL —流
<C++标准库>学习笔记 - STL -流 一.操控器 1.原理 2.自定义操控器 3.控制输入的宽度 二.自定义 I/O 操作符 1.重载输出操作符 2.输入操作符 三.自定义格式化标志 ...
- 线性代数库 Armadillo 学习笔记
线性代数库 Armadillo 学习笔记 项目环境 Xcode 项目配置 前置代码 矩阵基本操作 全零矩阵 全一矩阵 对角矩阵 打印一个矩阵 修改获取矩阵元素 获取某行某列 获取对角向量 转置矩阵 逆 ...
最新文章
- java并发订单号生成
- 成都黑帽门图片大全_2020成都第一波出行/便民/打卡免费大全
- 使用什么优化器_优化器怎么选?一文教你选择适合不同ML项目的优化器
- Assigning to Classes CodeForces - 1300B
- Java实例化对象过程中的内存分配
- Infor与Marketo®合作,帮助全球企业实现客户体验变革
- java docx 内存溢出_第2章 Java内存区域与内存溢出异常
- Cocos2D场景编辑器
- 发现同义词 python_python同义词替换的实现(jieba分词)
- [openwrt] ubus实现进程间通信举例
- TeamViewer 远程应用不显示,空白解决方案
- 2015年系统架构师考试题详解
- 【R语言文本挖掘】:n-grams和相关性计算
- UI设计教程分享之RGB与CMYK色彩模式对比
- kaggle员工离职预测——SVC
- Qt界面编程-Qt简介
- python中int() 按照“四舍五入”的方式取整
- 马云谈教师惩戒权:没有惩戒,就如让老师开一辆没刹车的汽车
- DIY时钟类--广州百田笔试之一
- EMAIL 如何发送给多人,如何CC,BCC?