c++ 中常见的缩写短语

1.小字符串对象空间优化,SSO(Small String Optimization):

对于短小字符串,c++ 标准库一般采用栈上分配,加快处理速度。可以使用heaptrack 测试程序的heap使用情况。

sso.cc:

#include <string>
#include <iostream>

int main(){
	std::string s{"hello,worldabcd-"};
	std::cout  <<  s.size() << '\n';
	return 0;
}
make  sso  &&  heaptrack  ./sso 

输出:

heaptrack --analyze "/tmp/heaptrack.sso.1321.gz" | grep -A1 -B1 basic_string

输出:

去掉末尾的字符'-', 重新运行:

 make sso  &&  heaptrack ./sso 

输出:

重新分析heap 使用情况:

heaptrack --analyze "/tmp/heaptrack.sso.1629.gz" | grep -A1 -B1 basic_string

无输出

上面是g++ 自带的libstdc++ 的情况,clang 的lib++, 搜索一下lib++的实现,libc++ 的短字符串表示, 64 位系统上结构体__long 的大小是24字节,普通string其实是basic_string<char>, 所以value_type 就是char, 所以最栈上buffer 大小是23字节。用heaptrack 检查一下:

clang-sso.cc:

#include <string>
#include <iostream>
int main(){
	std::string s{"hello,worldabcd-------+"};
	std::cout << s.size() << '\n';
	return 0;
}
clang++ -std=c++17 -stdlib=libc++ -nostdinc++  -I ~/workplace/llvm7/include/c++/v1/  -L ~/workplace/llvm7/lib/  -Wl,-rpath,/home/yj/workplace/llvm7/lib/    clang-sso.cc  -o  clang-sso
heaptrack  ./clang-sso
heaptrack --analyze "/tmp/heaptrack.clang-sso.4572.gz"|grep -B1 -A1  basic_string

输出:

去掉末尾的字符'+', 重新运行命令:

heaptrack  ./clang-sso
heaptrack --analyze "/tmp/heaptrack.clang-sso.5084.gz"|grep  -A1 -B1 basic_string

无输出

libstd++ buffer 大小16,去掉末尾的NUL,最大15字符, lib++ buffer 大小23,去掉末尾的NUL, 最大22 字符.

2.实参依赖的名称查找,ADL(Argument-dependent lookup):

#include <cstdio>
namespace A {
        struct X{};
        struct Y{};
        void f(int){
                puts("A::f");
        }
        void g(X){
                puts("A::g");
        }
        void h(Y) {
                puts("A:h");
        }
}  // namespace A

namespace B {
        void f(int i) {
                using A::f;
                f(i);  
        }
        void g(A::X x) {
                A::g(x);  
        }
        void h(A::Y y) {
	        B::h(y);  
        }
}  // namespace B

int main(){
        B::f(1);
	h(A::Y{});
	B::h(A::Y{});
        return 0;
}

输出:

A::f

A:h

Segmentation fault (core dumped)

第一句的输出很正常,显式调用namespace B 的 f, B的 f 内部明确宣布使用A 的f;第二句调用了namespace A 中的h, 这里ADL 起作用了,由于实参类型Y来自namespace A, 所以编译器选择了namespace A 中的h;第三 句由于无限递归,段错误了。

如果替换namespace B 中的 h定义:

 void h(A::Y y) {
		   h(y);  
 }

编译错误:

c++ 的overload set 有两个同名符号h,无法作出选择。更详细的解释例子

3. 模板元编程, TMP(Template Meta Programming):

这里使用Functional Programming in C++中的例子解释,非常不错的书,要说函数式编程,感觉rust 比 c++ 更适合, rust 的类型系统天生拒绝共享易变状态。

type_utils.h:

#ifndef UTILS_H
#define UTILS_H


// Utility class for debugging deduced types
template <typename T>
class error;


// Utility meta-function for detecting type validity
template <typename ...T>
using void_t = void;

// If you are using an older compiler,
// replace the above definition with the following:
//     template<typename... Ts> struct make_void { typedef void type;};
//     template<typename... Ts> using void_t = typename make_void<Ts...>::type;


// Meta-function that returns the type of an element
// in an iterable collection
template <typename T>
using contained_type =
    std::remove_cv_t<
        std::remove_reference_t<
            decltype(*std::begin(std::declval<T>()))
        >
    >;


// Meta-function that returns a type with references stripped
template <typename T>
struct remove_reference {
    using type = T;
};

template <typename T>
struct remove_reference<T&> {
    using type = T;
};

template <typename T>
struct remove_reference<T&&> {
    using type = T;
};

template <typename T>
using remove_reference_t = typename remove_reference<T>::type;


// Meta-function that detects whether a type
// has a nested `value_type` type definition
template < typename C
         , typename = void_t<>
         >
struct has_value_type : std::false_type {};

template<typename C>
struct has_value_type<C, void_t<typename C::value_type>> : std::true_type {};


template < typename C
         , typename = void_t<>
         >
struct is_iterable : std::false_type {};

template <typename C>
struct is_iterable
    <C, void_t < decltype(*std::begin(std::declval<C>()))
               , decltype(std::end(std::declval<C>()))
               >>
    : std::true_type {};


// Meta function that always returns false
// Useful with static_assert and if-constexpr
template <typename...>
struct false_: std::false_type {};

#endif /* !UTILS_H */

test.cpp:

#include <iostream>
#include <algorithm>
#include <numeric>

#include <vector>
#include <string>
#include <type_traits>

#include "type_utils.h"

// Function that sums all items in an iterable collection
template < typename C
         , typename R = contained_type<C>
         >
R sum_iterable(const C &collection)
{
    std::cout << "This is sum_iterable\n";
    return std::accumulate(
            begin(collection),
            end(collection),
            R());
}


// Function that sums all items in a collection
// that has a nested value_type definition
template < typename C
         , typename R = typename C::value_type
         >
R sum_collection(const C &collection)
{
    std::cout << "This is sum_collection\n";
    return std::accumulate(
            begin(collection),
            end(collection),
            R());
}



// Function that sums all items in a collection
// which first tries to use C::value_type and then
// contained_type<C>
template <typename C>
auto sum(const C &collection)
{
    if constexpr (has_value_type<C>()) {
        return sum_collection(collection);
    } else if constexpr (is_iterable<C>()) {
        return sum_iterable(collection);
    } else {
        static_assert(false_<C>(), "sum can be called only on collections");
    }
}


// Meta-function that checks whether two types
// are identical
template <typename T1, typename T2>
struct is_same: std::false_type {};

template <typename T>
struct is_same<T, T>: std::true_type {};


int main(int argc, char *argv[])
{
    // Uncomment this to make the compiler write the
    // exact result of the contained_type meta-function
    // error<contained_type<std::vector<std::string>>>();

    // Asserting that std::vector<std::string> contains
    // values of type std::string
    static_assert(
        is_same<
            contained_type<std::vector<std::string>>,
            std::string
        >(), "Expected the contained_type to return std::string");

    // Calling the function sum on a vector of ints.
    // The result will be an int
    std::vector<int> xs { 1, 2, 3, 4 };
    std::cout << sum_iterable(xs) << std::endl;
    std::cout << sum_collection(xs) << std::endl;
    std::cout << sum(xs) << std::endl;

    // Uncomment this to trigger the static_assert in the
    // sum function
    // sum(1);
	
    return 0;
}

这段代码主要是实现泛型函数sum, 可以求出容器内所有元素的和,或者某个可迭代类型的迭代出来的所有值的和。

template <typename C>
auto sum(const C &collection)
{
    if constexpr (has_value_type<C>()) {
        return sum_collection(collection);
    } else if constexpr (is_iterable<C>()) {
        return sum_iterable(collection);
    } else {
        static_assert(false_<C>(), "sum can be called only on collections");
    }
}

对于某个未知类型C, sum 函数首先检查类型C是否拥有成员类型value_type, 对于标准库中的容器,都导出了value_type , 如果没有遵循此惯例,使用is_iterable 检查C 类型是否可迭代, 否则编译报错。整个sum函数的决策都是在编译时作出,TMP 是一种编译时计算的技术。函数has_value_type, is_iterable被称作元函数,跟普通函数不太一样, 普通函数在值上运作, 元函数在类型上运作。元函数主要是用于查询修改类型的状态, 比如contained_type,使用了多阶段转换,萃取元素类型:

使用declval 获取T的临时引用(即使这个集合可能没提供默认构造函数,也可以调用其成员函数)

使用begin 获取指向集合中第一个元素的迭代器(begin 需要其参数类型提供begin成员函数)

用decltype计算,解引用迭代器后的类型, 使用remove_reference_t 去除类型可能包含的引用

使用remove_cv_t,去除类型可能包含的const 修饰

4. 替换失败不是错误,SFINAE(Substitution Failure Is Not An Error):

上面的求和的例子中,就包含SFINAE的使用:

template < typename C
         , typename = void_t<>
         >
struct has_value_type : std::false_type {};

template<typename C>
struct has_value_type<C, void_t<typename C::value_type>> : std::true_type {};

类模板has_value_type 被重载了两次(分别对应主模板和模板特化), 第一个overload处理C 类型内部没有嵌套value_type 的情况,第二个overload 处理C 类型内部存在嵌套value_type 的情况;当类型C 存在value_type 时,两个模板都匹配,但是特化的模板匹配更好, 选取了第二个可以被评估为true;当类型C 不存在value_type 时,编译器在计算void_t 时出错,导致在实例化第二个模板失败,但是编译器不把错误报告给用户,而是安静地从overload set 中,删除第二个模板, 此时只有第一个模板可用,被评估为false。

继承false_type, true_type的目的是让has_value_type 类型成为编译时函数对象, 此函数对象的返回值时false, true:

namespace std {
template<class T, T v>
struct integral_constant {
    static constexpr T value = v;
    typedef T value_type;
    typedef integral_constant type; 
    constexpr operator value_type() const noexcept { return value; }
    constexpr value_type operator()() const noexcept { return value; } 
};

template<bool B>
using bool_constant = integral_constant<bool, B>;

using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
}

5.指向实现的指针模式,PIMPL(Pointer to Implementation):

先看一个简单的widget 普通实现:

#include <iostream>
#include <string>
#include <string_view>

class Widget
{
	private:
		std::string text;
		int width = 0;
		int height = 0;
		bool visible = true;

		void draw()
		{
			std::cout 
				<< "widget " << '\n'
				<< "  visible: " << visible << '\n'
				<< "  size: " << width << ", " << height << '\n'
				<< "  text: " << text << '\n';
		}
	public:
		void set_text(std::string_view t)
		{
			text = t.data();
			draw();
		}

		void resize(int const w, int const h)
		{
			width = w;
			height = h;
			draw();
		}

		void show() 
		{ 
			visible = true; 
			draw();
		}

		void hide() 
		{ 
			visible = false; 
			draw();
		}
};

int main(){
	Widget w;
	w.resize(100, 20);
	w.set_text("sample");
	w.hide();

	Widget  w2 = w;
	w2.show();

	Widget w3 = std::move(w2);
	w3.hide();
	return 0;
}

pimpl 模式主要是为了让class接口与实现的分离,让class 的客户减少对class 实现细节的依赖,降低编译时间,通常使用unique_ptr实现自动内存管理:

widget.h:

#ifndef  _Widget_include
#define  _Widget_include

#include <memory>
#include <string_view>

class Widget
{
    private:
        class impl;
std::unique_ptr<impl> pimpl;

    public:
	Widget();
	~Widget();

	Widget(Widget && other) noexcept;
	Widget& operator=(Widget && other) noexcept;

	Widget(const Widget& other);
	Widget& operator=(const Widget& other);

	void set_text(std::string_view text);
	void resize(int const w, int const h);
	void show();
	void hide();
};

#endif

widget.cc:

#include "widget.h"
#include <iostream>
#include <string>
#include <string_view>

class Widget::impl
{
	std::string text;
	int width = 0;
	int height = 0;
	bool visible = true;

	void draw()
	{
		std::cout 
			<< "widget " << '\n'
			<< "  visible: " << visible << '\n'
			<< "  size: " << width << ", " << height << '\n'
			<< "  text: " << text << '\n';
	}
	public:
	void set_text(std::string_view t)
	{
		text = t.data();
		draw();
	}

	void resize(int const w, int const h)
	{
		width = w;
		height = h;
		draw();
	}

	void show() 
	{ 
		visible = true; 
		draw();
	}

	void hide() 
	{ 
		visible = false; 
		draw();
	}
};


Widget::Widget() :
	pimpl(std::make_unique<impl>())
{}

Widget::~Widget() = default;

Widget::Widget(Widget &&) noexcept = default;
Widget& Widget::operator=(Widget &&) noexcept = default;

Widget::Widget(const Widget& other)
	: pimpl(
			std::make_unique<impl>(*other.pimpl))
{}

Widget& Widget::operator=(const Widget& other) 
{
	if (this != &other) 
	{
		pimpl = std::make_unique<impl>(*other.pimpl);
	}
	return *this;
}

void Widget::set_text(std::string_view text)
{
	pimpl->set_text(text);
}

void Widget::resize(int const w, int const h)
{
	pimpl->resize(w, h);
}

void Widget::show()
{
	pimpl->show();
}

void Widget::hide()
{
	pimpl->hide();
}

pimpl.cc:

#include "widget.h"

int main(){
         Widget w;
         w.resize(200, 150);
         w.set_text("new impl");
         w.hide();

         Widget w2 = w;
         w2.show();

         Widget w3 = std::move(w2);
         w3.hide();
	return 0;
}
g++  -std=c++17  -Wall   widget.h  widget.cc  pimpl.cc     -o  pimpl

6.CRTP(Curiously Recurring Template Pattern ):

crtp 使用模版模拟类型层级, 不需要虚函数的开销,实现了静态分发。

#include <iostream>

template <typename T>
class Widget
{
	T* derived() { return static_cast<T*>(this); }
	public:
	void draw()
	{
		derived()->drawLine();
		derived()->fillColor();
	}
};

class Menu: public Widget<Menu>{
	public:
		void drawLine(){
			std::cout << "drawLine in menu" << '\n';
		}
		void fillColor(){
			std::cout << "fillColor in menu" << '\n';
		}
};

class CheckBox: public Widget<CheckBox>{
	public:
		void drawLine(){
			std::cout << "drawLine in checkbox" << '\n';
		}
		void fillColor(){
			std::cout << "fillColor in checkbox" << '\n';
		}
};

template<typename T>
void drawWidget(Widget<T>& w) {
	w.draw();
}

int main(){
	CheckBox c;
	Menu   m;

	drawWidget(c);
	drawWidget(m);

	return 0;
}

但是使用crtp的类型实际上没有一个共同的祖先,所以不能被收集到一个容器内,克服方法是引入一个抽象基类(ABC),然后crtp 模板继承abc, 这样所有使用这个crtp的类型都去实现了某个共同的接口。

#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>

using namespace std;

class WidgetBase {
	public:
		virtual void draw() = 0;
		virtual ~WidgetBase() = default;
};

template <typename T>
class Widget : public  WidgetBase{
	T* derived() { return static_cast<T*>(this); }
	public:
	virtual void draw() override
	{
		derived()->drawLine();
		derived()->fillColor();
	}
};

class Menu: public Widget<Menu>{
	public:
		void drawLine(){
			std::cout << "drawLine in menu" << '\n';
		}
		void fillColor(){
			std::cout << "fillColor in menu" << '\n';
		}
};

class CheckBox: public Widget<CheckBox>{
	public:
		void drawLine(){
			std::cout << "drawLine in checkbox" << '\n';
		}
		void fillColor(){
			std::cout << "fillColor in checkbox" << '\n';
		}
};

template<typename T>
void drawWidget(Widget<T>& w) {
	w.draw();
}

int main(){
	vector<unique_ptr<WidgetBase>>  vs;
	for(size_t i = 0;  i < 5; i++) {
			vs.emplace_back(make_unique<Menu>());
			vs.emplace_back(make_unique<CheckBox>());
	}
	for_each(begin(vs), end(vs),  [](auto& e) {
		e->draw();
	});
	return 0;
}

crtp 可以被用来制造多个线程安全的单例类型

#include <iostream>

template <class T>
class SingleBase
{
	protected:
		SingleBase() = default;
	public:
		SingleBase(SingleBase const &) = delete;
		SingleBase& operator=(SingleBase const&) = delete;

		static T& instance()
		{
			static T single;
			return single;
		}
};

class SingleOne : public SingleBase<SingleOne>
{
	SingleOne() = default;
	friend class SingleBase<SingleOne>;
	public:
	void method() { std::cout << "some method from SingleOne" << '\n'; }
};

class SingleTwo : public SingleBase<SingleTwo>
{
	SingleTwo() = default;
	friend class SingleBase<SingleTwo>;
	public:
	void method() { std::cout << "some method from SingleTwo" << '\n'; }
};

int main(){
	SingleOne::instance().method();
	SingleTwo::instance().method();
	return 0;
}

7.EBO(Empty Base Optimization):

#include <cassert>

struct E1 {};
struct E2 {};

struct Foo {
  E1 e1;
  E2 e2;
  int data;
}; 

struct Bar : public  E1, public  E2{
  int data;
};

int main(){
	assert(sizeof(Foo) == sizeof(int)*2);
	assert(sizeof(Bar) == sizeof(int));
}

Foo 类型中包含两个空类, c++ 标准规定空类也要分配存储,以保持多个相同空类型的对象的身份,但是把类型E1,E2提升成为父类型, 标准又允许编译器把E1,E2优化掉。但是EBO 能不能开启,要看场景:

#include <cassert>
 
struct Base {}; 
 
struct Derived1 : Base {
    int i;
};

struct Derived2 : Base {
    Base c; 
    int i;
};

struct Derived3 : Base {
    Derived1 c; 
    int i;
};
 
int main()
{
    assert(sizeof(Base) == 1);
    assert(sizeof(Derived1) == sizeof(int));
    assert(sizeof(Derived2) == 2*sizeof(int));
    assert(sizeof(Derived3) == 3*sizeof(int));
    return 0;
}

Derived1 可以开启,Derived1 的对象中包含一个Base 子对象,不需要区分身份; Derived2 不能开启,Derived2 的对象中包含两个Base 子对象,需要区分身份;Derived3 不能开启,Derived3 的对象中包含三个Base 子对象,需要区分身份。标准库和boost 等有多处使用EBO, 比如compressed_pair

8.RVO,NRVO(Return Value Optimization, Named Return Value Optimization):

主要是对象作为函数返回值时,让编译器找机会, 看看能不能把拷贝构造或者移动构造消除掉, 毕竟c++是一个强调值语义的编程语言,需要编译器配合一下,让值语义编程更高效,这里有详细的解释

9.RAII(Resource Acquisition Is Initialization):

#include <cstdio>

class File final
{
public:
	File(std::FILE* file);
	~File();

	File(const File& src) = delete;
	File& operator=(const File& rhs) = delete;

	File(File&& src) noexcept = default;
	File& operator=(File&& rhs) noexcept = default;

	std::FILE* get() const noexcept;
	std::FILE* release() noexcept;
	void reset(std::FILE* file = nullptr) noexcept;

private:
	std::FILE* mFile;
};

File::File(std::FILE* file) : mFile(file)
{
}

File::~File()
{
	reset();
}

std::FILE* File::get() const noexcept
{
	return mFile;
}

std::FILE* File::release() noexcept
{
	std::FILE* file = mFile;
	mFile = nullptr;
	return file;
}

void File::reset(std::FILE* file) noexcept
{
	if (mFile) {
		fclose(mFile);
	}
	mFile = file;
}

int main()
{
	File myFile(fopen("file.txt", "r"));
	return 0;
}

raii 在c++ 中无处不在, 安全资源管理必备, 一般资源管理类没有copy语义(除非可共享的资源如share_ptr), 可以有move语义。

10. SOO(Small Object Optimization):

soo 跟 sso 类似, 对于小对象尽量分配在栈上,标准库 std::function 就是一个应用soo 的例子,std::function 需要分配单独的存储,保存有状态的可调用对象的状态,指的是内部有数据成员的函数对象, 或者捕获了变量的lambda expr.

#include <cstdio>
#include <functional>
#include <string>
using namespace std;

class F {
	private:
		static constexpr int  bufsize = 17;
		char arr[bufsize];
	public:
		void operator()(){
			puts("call operator");
		}
};

int main()
{
	F f;	
	function<void()> fc = f;
	fc();
	printf("sizeof(F) = %lu\n", sizeof(F));
	return 0;
}

使用bufsize=17,编译运行:

heaptrack   ./sbo
heaptrack -a  heaptrack.sbo.13493.gz | grep  -A1 -B1  '[[:digit:]]\+B peak'

使用bufsize=16,编译运行:

heaptrack  ./sbo
 heaptrack -a  heaptrack.sbo.13493.gz | grep  -A1 -B1 '[[:digit:]]\+B peak'

无输出

clang 的lib++ 实现的std::function 也使用了soo 或者叫 sbo ,但是阀值跟libstd++ 不同,lib++ soo 实现

使用bufsize=25,编译运行:

heaptrack  ./clang-sbo
heaptrack -a  heaptrack.clang-sbo.15561.gz |grep -A1 -B1  '[[:digit:]]\+B peak'

使用bufsize=24,编译运行:

heaptrack  ./clang-sbo
 heaptrack -a  heaptrack.clang-sbo.15734.gz |grep -A1 -B1  '[[:digit:]]\+B peak'

无输出

转载于:https://my.oschina.net/evilunix/blog/3032325

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值