文章目录

  • 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++网络库的学习笔记相关推荐

  1. 《muduo网络库》学习笔记——muduo学习总结

    muduo是基于非阻塞的IO和事件驱动的网络库(Reactor模式),其核心是一个事件循环EventLoop,用于响应计时器和IO事件.muduo采用基于对象(object-based)而非面向对象( ...

  2. 《Linux多线程服务端编程:使用muduoC++网络库》学习笔记

    文章目录 第1章 线程安全的对象生命期管理 1.1 当析构函数遇到多线程 1.1.1 线程安全的定义 1.1.3 线程安全实例 1.2 对象的创建很简单 1.3 销毁很难 1.4 线程安全的Obser ...

  3. 《muduo网络库》学习笔记——时间轮Timeing wheel

    目录 1. 概述 2. Timing wheel 原理 2.1 连接超时被踢掉的过程 2.2 连接刷新 2.3 多个连接 3. 代码实现与分析 1. 概述 本文介绍如何使用 timing wheel ...

  4. 《Linux多线程服务端编程——使用muduo C++网络库》读书笔记

    第一章 线程安全的对象生命期管理 第二章 线程同步精要 第三章 多线程服务器的适用场合与常用编程模型 第四章 C++多线程系统编程精要 1.(P84)11个常用的最基本Pthreads函数: 2个:线 ...

  5. Lib库使用学习笔记

    Lib库使用学习笔记 转自:http://blog.csdn.net/macky0668/article/details/6044867 技术前沿 2008-03-31 14:21:10 阅读177  ...

  6. 【Python-pywt】 小波变化库—Pywavelets 学习笔记

    (转载) [Python ]小波变化库--Pywavelets 学习笔记_nanbei2463776506的博客-CSDN博客 https://blog.csdn.net/nanbei24637765 ...

  7. 《Linux多线程服务端编程:使用muduo C++网络库》书摘6.6.2节

    6.6.2 常见的并发网络服务程序设计方案 W. Richard Stevens 的<UNIX 网络编程(第2 版)>第27 章"Client-ServerDesign Alte ...

  8. 《C++标准库》学习笔记 — STL —流

    <C++标准库>学习笔记 - STL -流 一.操控器 1.原理 2.自定义操控器 3.控制输入的宽度 二.自定义 I/O 操作符 1.重载输出操作符 2.输入操作符 三.自定义格式化标志 ...

  9. 线性代数库 Armadillo 学习笔记

    线性代数库 Armadillo 学习笔记 项目环境 Xcode 项目配置 前置代码 矩阵基本操作 全零矩阵 全一矩阵 对角矩阵 打印一个矩阵 修改获取矩阵元素 获取某行某列 获取对角向量 转置矩阵 逆 ...

最新文章

  1. java并发订单号生成
  2. 成都黑帽门图片大全_2020成都第一波出行/便民/打卡免费大全
  3. 使用什么优化器_优化器怎么选?一文教你选择适合不同ML项目的优化器
  4. Assigning to Classes CodeForces - 1300B
  5. Java实例化对象过程中的内存分配
  6. Infor与Marketo®合作,帮助全球企业实现客户体验变革
  7. java docx 内存溢出_第2章 Java内存区域与内存溢出异常
  8. Cocos2D场景编辑器
  9. 发现同义词 python_python同义词替换的实现(jieba分词)
  10. [openwrt] ubus实现进程间通信举例
  11. TeamViewer 远程应用不显示,空白解决方案
  12. 2015年系统架构师考试题详解
  13. 【R语言文本挖掘】:n-grams和相关性计算
  14. UI设计教程分享之RGB与CMYK色彩模式对比
  15. kaggle员工离职预测——SVC
  16. Qt界面编程-Qt简介
  17. python中int() 按照“四舍五入”的方式取整
  18. 马云谈教师惩戒权:没有惩戒,就如让老师开一辆没刹车的汽车
  19. DIY时钟类--广州百田笔试之一
  20. EMAIL 如何发送给多人,如何CC,BCC?

热门文章

  1. 理解W3C是干什么的
  2. 【图像转换】基于matlab灰度图像转换彩色图像【含Matlab 1233期】
  3. 安卓android银联支付Unionpay
  4. QT的QTreeWidget节点触发事件(一)
  5. 色彩敏感度测试 l 据说只有1%的设计师能全对,不服来战!
  6. Ubuntu 笔记本麦克风没有声音解决方法
  7. Springboot快速开发-书本信息管理系统(项目源码)
  8. 刘可 寂寞才说爱歌曲试听 刘可 寂寞才说爱MP3歌词
  9. 机器学习元老之间的关系图
  10. MathType 6.9中平行四边形符号如何输入