侯捷老师C++课程:C++2.0 新特性

C++ 2.0 新特性

第一讲:语言

variatic templates 参数包

在类模板中,模板参数包必须是最后一个模板形参. 而在函数模板中则不必!!!

image-20230427170952730

这个之前提过了,就不细谈了

下面那三个分别对应:

typename... Types //模板参数包
const Types&... args //函数参数类型包
print(args...) //函数参数包

利用参数包也可以实现万用的hashcode的实现: 之前写过就不细看了

image-20230427171431489

零碎知识点

nullptr
#include <iostream>
using namespace std;

void f(int)
{
    cout << "call of int" << endl;
}

void f(void *)
{
    cout << "call of void*" << endl;
}

int main()
{
    f(0); // calls f(int)
    // f(NULL);    // 这里会报错,因为NULL既可以指是int 也可以是指针
    f(nullptr); // calls f(void*)

    return 0;
}
auto

提醒:不要有了auto就以后都不写类型了,能不用就不用,除非是在类型名太长或者太复杂的类型才用一下,我们心里需要明白这到底是怎么类型,不要编译器知道了我们不知道

#include <iostream>
using namespace std;

//函数的返回值也可以是auto
auto Func(const int &val)
{
    return val > 0;
}

int main()
{
    // 注意函数指针的写法
    auto func = [](const int &val) -> bool
    {
        return val > 0;
    };
    bool (*func2)(const int &val) = Func;

    cout << func(1) << endl;
    cout << func2(-1) << endl;

    return 0;
}

initializer_list<>

uniform initialization 统一初始化

任何初始化动作都可以用一个共同语法:{ //填入值 }

int values[] {1,2,3};
vector<string>cities{
"Berlin","New York","London"
};

示例代码:

#include <iostream>
using namespace std;
#include <vector>

template <typename Container>
inline void print(const Container &con)
{
    for (auto val : con)
        cout << val << ' ';
    cout << endl;
}

int main()
{
    vector<int> v{1, 2, 3};
    vector<string> cities{
        "Berlin", "New York", "London"};
    print(v);
    print(cities);

    return 0;
}

在编译器看到 {} 的时候会自动创建出来一个 initializer_list,这是一个类,具体代码实现如下:

template <class _E>
// 这个东西背后是一个 array ,编译器在看见大括号的时候就会预先准备一个 array
class initializer_list
{
public:
    typedef _E value_type;
    typedef const _E &reference;
    typedef const _E &const_reference;
    typedef size_t size_type;
    typedef const _E *iterator;
    typedef const _E *const_iterator;

private:
    iterator _M_array;
    size_type _M_len;

    // The compiler can call a private constructor.
    // 编译器在这里能调用私有的构造函数(编译器可以,我们不可以)
    // 到这里会把array的头指针和长度传递给array参数,本身并没有内含array(有点像委托)
    constexpr initializer_list(const_iterator __a, size_type __l)
        : _M_array(__a), _M_len(__l) {}

public:
    constexpr initializer_list() noexcept
        : _M_array(0), _M_len(0) {}

    // Number of elements.
    constexpr size_type
    size() const noexcept { return _M_len; }

    // First element.
    constexpr const_iterator
    begin() const noexcept { return _M_array; }

    // One past the last element.
    constexpr const_iterator
    end() const noexcept { return begin() + size(); }
};

关于这个类的拷贝构造,可以看由于在类里面没有对拷贝构造的重写,导致两个initializer_list在拷贝的时候是浅拷贝,两个指针指向同一块内存空间,可能会出现危险,这个需要注意

STL的容器是如何引入initializer_list的?

image-20230430111248498

initializer_list<>里面内置了一个array数组的指针和这个数组的长度,编译器会读取{}里面的元素来进行容器的插入操作以实现初始化操作

示例代码:

#include <iostream>
using namespace std;
#include <vector>
#include <initializer_list>
#include <string>

class Algorithm
{
public:
    // 传入的是一个initializer_list<>
    template <typename Value_Type>
    inline Value_Type
    _min(const initializer_list<Value_Type> &init_list)
    {
        return Min(init_list.begin(), init_list.end());
    }

    template <typename Value_Type>
    inline Value_Type
    _max(const initializer_list<Value_Type> &init_list)
    {
        return Max(init_list.begin(), init_list.end());
    }

private:
    template <typename Input_Iterator>
    inline typename iterator_traits<Input_Iterator>::value_type
    Min(Input_Iterator first, Input_Iterator last)
    {
        auto Min = *first;
        for (; first != last; ++first)
            Min = Min <= *first ? Min : *first;
        return Min;
    }

    template <typename Input_Iterator>
    inline typename iterator_traits<Input_Iterator>::value_type
    Max(Input_Iterator first, Input_Iterator last)
    {
        auto Max = *first;
        for (; first != last; ++first)
            Max = Max >= *first ? Max : *first;
        return Max;
    }
};

template <typename Container>
inline void print(const Container &con)
{
    for (auto val : con)
        cout << val << ' ';
    cout << endl;
}

int main()
{
    vector<int> v1{2, 5, 7, 13, 69, 83, 50};
    vector<int> v2({2, 5, 7, 13, 69, 83, 50});
    vector<int> v3;
    v3 = {2, 5, 7, 13, 69, 83, 50};
    v3.insert(v3.begin() + 2, {0, 1, 2, 3, 4});
    print(v3);

    cout << Algorithm()._max({54, 16, 48, 5}) << endl;
    cout << Algorithm()._min({string("Ace"), string("Hello"), string("Fuck"), string("Zion")}) << endl;

    return 0;
}

explicit

explicit for ctor taking one argument

#include <iostream>
using namespace std;

struct Complex
{
    int real, imag;

    explicit Complex(int re, int im = 0) : real(re), imag(im) {}
    //explict关键字的含义 防止类构造函数的隐式自动转换
    //就是说这里由于只需要传入一个参数,所以编译器很可能会把数字隐式转化为Complex对象
    //但是加上了explict之后,明确指出不要让编译器这么干,要生成Complex对象只能显式调用构造函数!!!!

    Complex operator+(const Complex &x)
    {
        return Complex(real + x.real, imag + x.imag);
    }
};

inline ostream &
operator<<(ostream &os, const Complex &x)
{
    os << '(' << x.real << ',' << x.imag << ')';
    return os;
}

int main()
{
    Complex c1(12, 5);
    // Complex c2 = c1 + 5; // 加了explicit关键字就不允许编译器直接把5转化为 Complex 类型了
    cout << c1 << endl;

    return 0;
}

这是一个实参加上 explicit 关键字的情况,前面已经提过很多了

explicit for ctors taking more than one argument

#include <iostream>
using namespace std;
#include <initializer_list>

struct P
{
    P(int a, int b) { cout << "P (int a , int b) " << endl; }
    // P(initializer_list<int>) { cout << "P (initializer_list<int>) " << endl; }
    explicit P(int a, int b, int c) { cout << "explicit P (int a , int b , int c) " << endl; }
};

int main()
{
    P p1(77, 5);
    P p2{77, 5};
    P p3 = {77, 5};

    P p4{77, 5, 42};
    // 这个是可以的,因为它既可以看作传入了三个参数,也可以看作传入了初始化序列
    // 而如果像下面一样加上括号并且有 explicit 关键字就只能传入 三个参数的形式

    P p5({77, 5, 42}); 
    // 这个在有 explicit 关键字的情况下没有办法把 initializer_list 的形式转化为 a,b,c 的形式,会报错

    return 0;
}

=delete,=default

#include <iostream>
using namespace std;

// 如果已经定义了一个ctor,那么编译器就不会给一个默认的ctor
class Zoo
{
public:
    Zoo(int i1, int i2) : d1(i1), d2(i2) {}
    Zoo(const Zoo &) = delete; // delete表示我不要这一个重载
    Zoo(Zoo &&) = default;     // default表示我需要这一个重载并且是编译器默认提供给我的这个重载
    Zoo &operator=(const Zoo &) = default;
    Zoo &operator=(const Zoo &&) = delete;

    virtual ~Zoo() {}

private:
    int d1, d2;
};

int main()
{
    Zoo z1(1, 2);
    // Zoo z2(z1); // 无法使用因为他是已删除的函数

    return 0;
}

一般是应用在 Big 3 上面,即 构造函数,拷贝构造,拷贝赋值和析构函数

image-20230504191205113

其中出现了右值引用,这个目前不了解

Zoo(const Zoo&)=delete;// copy ctor
Zoo(Zoo&&)=default;// move ctor

一个更细的例子:

image-20230504191639232

#include <iostream>
using namespace std;

class Foo
{
public:
    // ctor
    Foo(int i) : _i(i) {}
    Foo() = default;

    // copy ctor
    Foo(const Foo &x) : _i(x._i) {}
    // Foo(const Foo &) = default; // error 都已经定义出来了还要默认的,不行
    // Foo(const Foo &) = delete;  // error 都已经定义出来又不要了,不行

    // copy assign
    Foo &operator=(const Foo &x)
    {
        _i = x._i;
        return *this;
    }

    // Foo &operator=(const Foo &x) = default; // error 都已经定义出来了还要默认的,不行
    // Foo &operator=(const Foo &x) = delete;  // error 都已经定义出来又不要了,不行

    // void func1() = default; // error 一般的函数没有默认版本,只能用于 big five上面
    void func2() = delete; // delete可以用在任何函数上面(=0 只能用于 virtual 函数)

    // ~Foo() = delete;//error 不能删除析构函数,这会导致使用Foo对象错误!!!!
    ~Foo() = default;

private:
    int _i;
};

int main()
{
    Foo f1; // 如果不写 Foo() = default 编译器不会提供默认构造函数,会报错
    Foo f2(5);
    Foo f3(f1);
    f3 = f2;

    return 0;
}

对于一个空的类,编译器在处理的时候会提供默认的big 3,即 构造函数,拷贝构造,拷贝赋值,析构函数

class Empty{ };

//空的,但是编译器会提供
class Empty{
public:
    //ctor
    Empty(){ ... }
    //copy ctor
    Empty(const Empty& rhs){ ... }
    //copy assign
    Empty& operator=(const Empty& rhs){ ... }
    //dctor
    ~Empty(){ ... }
}

//以下代码对于一个空类是合法的
{
    Empty e1;
    Empty e2(e1);
    e2=e1;
}

那么我们怎么确认是用默认的还是自己写的呢?

classes with or without pointer members!!!

带有指针的类基本上都需要重写 big 3;不带指针的基本都不需要写!!!

No-Copy and Private-Copy

image-20230504201206953

#include <iostream>
using namespace std;

struct Nocopy
{
    Nocopy() = default;
    Nocopy(const Nocopy &) = delete;            // no copy
    Nocopy &operator=(const Nocopy &) = delete; // no assign
    ~Nocopy() = default;
};

struct NoDtor
{
    NoDtor() = default;
    ~NoDtor() = delete; // 非常不建议这么去做
};

void testNoDtor()
{
    // NoDtor nd;//栈区对象的生命周期在这个函数结束就销毁了,这时候会自动调用dtor,没有则报错
    NoDtor *p = new NoDtor; // 动态开辟是允许的,但是无法销毁
    // delete p;               // 不允许销毁
}

class PrivateCopy
{
private:
    // 这个类无法被一般的代码调用,但是可以被friend和member调用copy
    // 如果要禁止,不仅需要放到private里面,还要加上 = delete
    PrivateCopy(const PrivateCopy &);
    PrivateCopy &operator=(const PrivateCopy &);

public:
    PrivateCopy() = default;
    ~PrivateCopy();
};

int main()
{
    testNoDtor();

    return 0;
}

Alias(化名) Template (template typedef) 模板的化名

image-20230504202940442

值得注意的是下面两个没办法实现我们想要的结果!!!

test_moveable函数测试

这么写始终会报错,看起来是没有办法把容器和容器模板的类型分开来进行传入的

image-20230504213412890

所以可以这么写:

image-20230504213510643

template template parameter 双重模板参数

image-20230505102818021

#include <iostream>
using namespace std;
#define SIZE 1e6
#include <string>
#include <vector>
#include <list>
#include <deque>

template <typename Type>
inline void output_static_data(const Type &obj)
{
    cout << "static_data: " << endl; // 输出静态成员
}

// template template paremeter 双重模板参数
template <class Value_Type,
          template <class> // 这样写表示 Container模板使用 Value_Type 类型
          class Container>
// 这里由于传入的是容器,绝大多数的容器都有两个参数,第一个是元素类型,第二个是分配器,然而分配器又是以元素类型的模板
// 编译器无法推导第二个分配器的参数,虽然有默认值,所以就需要用到 Alias 来设置
class XCls
{
private:
    Container<Value_Type> c;

public:
    XCls()
    {
        for (long i = 0; i < SIZE; ++i)
            c.insert(c.end(), Value_Type());

        output_static_data(Value_Type());
        Container<Value_Type> c1(c);
        Container<Value_Type> c2(std::move(c));
        c1.swap(c2);
    }
};

#include <ext/pool_allocator.h>
namespace Alias
{
    template <typename Value_Type>
    using Vec = vector<Value_Type, __gnu_cxx::__pool_alloc<Value_Type>>;

    template <typename Value_Type>
    using Lst = list<Value_Type, __gnu_cxx::__pool_alloc<Value_Type>>;

    template <typename Value_Type>
    using Deq = deque<Value_Type, __gnu_cxx::__pool_alloc<Value_Type>>;
}

using namespace Alias;
int main()
{
    XCls<string, Vec> c;
    XCls<string, Lst> c2;
    XCls<string, Deq> c3;

    return 0;
}

type alias 类型化名

type alias 和 typedef 没有任何的不同

#include <iostream>
using namespace std;
#include <vector>

// type alias 和 typedef 没有任何的不同
namespace Test
{
    void test01(int, int)
    {
        cout << "test01" << endl;
    }

    template <typename T>
    struct Container
    {
        using Value_Type = T;
    };

    template <class CharT>
    using mystring = std::basic_string<CharT, std::char_traits<CharT>>;

    template <class Container>
    void fn2(const Container &con)
    {
        using Value_Type = typename iterator_traits<typename Container::iterator>::value_type;
        cout << "fn2" << endl;
    }
}

using namespace Test;
int main()
{
    // func现在指向参数如下的函数
    using func = void (*)(int, int);
    func f1 = test01;
    f1(1, 1);

    mystring<char> str;

    fn2(vector<int>());

    return 0;
}

noexcept 保证不会抛出异常

我们必须通知C++(特别是 std::vector),move ctor 和 move assignment 和 dtor不会抛出异常,前两个都是右值引用

以vector为例,vector容器在扩充空间的时候,是以2倍空间扩充,需要新找一块内存将当前的数据移动到新数据块中,这就需要用到 move ctor,并且如果不是noexcept,vector不敢调用它,只有是noexcept的时候vector才会调用它

注意:growable containers只有两种:vector和deque

image-20230505152557462

至于move ctor和move assignment,到后面再说

override 覆写 特用于虚函数重写上面

这个需要保证子类和父类这个虚函数的名称,返回值,参数类型,个数,位置完全相同!!!

#include <iostream>
using namespace std;

struct Base
{
    virtual void func(float) { cout << "Base func float" << endl; }
};

struct Derived1 : public Base
{
    // 第一个是定义了一个新的虚函数,不是override
    virtual void func(int) { cout << "Derived1 func int" << endl; }
    // 第二个才是上面父类的override
    virtual void func(float) override { cout << "Derived1 func float" << endl; }
};

int main()
{
    Derived1().func(1.1);

    return 0;
}

final

用来修饰class表示不允许类继承自己;用来修饰虚函数virtual表示不允许子类override这个函数

#include <iostream>
using namespace std;

struct Base1 final // final表示不允许有类继承自己
{
};

//  error
// struct Derived1 : Base1
// {
// };

struct Base2
{
    virtual void f() final; // final表示不允许子类覆写这个函数
};

struct Derived2 : Base2
{
    // void f(); //error
};

int main()
{

    return 0;
}

decltype

用来得到一个表达式的类型,有三大应用:

1.declare return types

#include <iostream>
using namespace std;

namespace Test {
template <typename Value_Type1, typename Value_Type2>
auto add(const Value_Type1& x, const Value_Type2& y)
    -> decltype(x + y) {  // 不写在前面是因为编译器先后次序编译不认识x,y,所以放在后面用 -> 来指明auto的类型
    return x + y;
}
}  // namespace Test

using namespace Test;
int main() {
    cout << add(1, 2) << endl;
    cout << add(1.1, 2) << endl;

    return 0;
}

2.in metaprogramming 元编程 就是用在泛型里面

3.lambdas

一个应用:

#include <iostream>
using namespace std;
#include <set>
#include <string>

namespace Test {

class Person {
public:
    Person() = default;
    Person(string firstname, string lastname)
        : _firstname(firstname), _lastname(lastname) {}

public:
    string _firstname;
    string _lastname;
};

ostream&
operator<<(ostream& os, const Person& p) {
    os << '(' << p._firstname << ',' << p._lastname << ')';
    return os;
}

auto CmpPerson = [](const Person& p1, const Person& p2) {
    return (p1._lastname < p2._lastname) ||
           (p1._lastname == p2._lastname) && (p1._firstname < p2._firstname);
};

struct Cmp
    : binary_function<Person, Person, bool> {
    // 被比较的不能被修改,编译器非常灵敏,需要加上const
    bool operator()(const Person& p1, const Person& p2) const {
        return (p1._lastname < p2._lastname) ||
               (p1._lastname == p2._lastname) && (p1._firstname < p2._firstname);
    }
} cmps;

template <typename Container>
inline void print(const Container& con) {
    for (auto val : con)
        cout << val << ' ';
    cout << endl;
}

}  // namespace Test

using namespace Test;
int main() {
    Person p1("John", "Wall");
    Person p2("David", "Paul");
    Person p3("Steve", "Paul");

    // 这里需要如果括号里不给CmpPerson参数,会调用CmpPerson的默认构造函数,不幸的是没有默认构造,所以需要给出
    set<Person, decltype(CmpPerson)> s({p1, p2, p3}, CmpPerson);
    print(s);
    return 0;
}
image-20230505195009665

lambdas

image-20230505190504411

[ ]里可以指定是以 value 还是以 reference 的形式传入,( )后面那三个东西是可选的,但是只要有一个出现那么( )就必须写出来,所以建议都写上( )

#include <iostream>
using namespace std;

int main() {
    []() -> void {
        cout << "hello lambda" << endl;
    }();  // 前三个是格式 最后一个括号代表调用

    auto I = []() -> void {
        cout << "hello lambda" << endl;
    };
    I();

    int id1 = 0, id2 = 0;
    // 为什么下面打印出来是0 1 2
    // 因为这里的id1传进去是0,还没走到下面
    // 由于是 value 传递,所以是copy操作,内部的id不会影响外面的id
    auto f = [id1, &id2]() mutable {
        // 如果不写 mutable ,这个id进来之后只能read only,不能++
        cout << "id1: " << id1 << ',' << "id2: " << id2 << endl;
        ++id1;
        ++id2;
    };

    // 上面lambda表达式的相对接近的写法(不对等,有小区别)
    // class Functor {
    // private:
    //     int id1;  // copy of outside id1
    //     int id2;  // reference of outside id2

    // public:
    //     void operator()() {
    //         cout << "id1: " << id1 << ',' << "id2: " << id2 << endl;
    //         ++id1;
    //         ++id2;
    //     }
    // };
    // Functor f;

    id1 = 42, id2 = 42;
    f();  // 0 42
    f();  // 1 43
    f();  // 2 44
    cout << id1 << ' ' << id2 << endl;

    return 0;
}

与上一个的例子联系起来,也让我们对set的底层实现有了更多的理解

这也解释了为什么在传入lambda的时候需要在括号里面指定这个函数变量,看他的构造就行了

image-20230505195056962

所以在functor和lambda之后,选择functor显然会稍微好一点

另一个例子:

#include <iostream>
using namespace std;
#include <algorithm>
#include <vector>

class LambdaFunctor {
public:
    LambdaFunctor(int x, int y)
        : _x(x), _y(y) {}

    bool operator()(int val) {
        return val > _x && val < _y;
    }

private:
    int _x;
    int _y;
};

template <typename Value_Type>
inline void printVector(const vector<Value_Type>& vec) {
    for (auto val : vec)
        cout << val << ' ';
    cout << endl;
}

int main() {
    int x = 30, y = 100;

    vector<int> v1{5, 28, 50, 83, 70, 590, 245, 59, 24};
    vector<int> v2{5, 28, 50, 83, 70, 590, 245, 59, 24};

    // 注意remove系列操作是假remove,需要erase才能真正删除
    auto newEnd1 = remove_if(v1.begin(), v1.end(), [x, y](int val) {
        return val > x && val < y;
    });
    v1.erase(newEnd1, v1.end());

    v2.erase(remove_if(v1.begin(), v1.end(), LambdaFunctor(x, y)), v2.end());

    printVector(v1);
    printVector(v2);

    return 0;
}

variadic templates

之前已经提到过很多次了,举一些例子:

#include <iostream>
using namespace std;

static int value = 0;

namespace Test {
inline void _func() {}

template <typename Value_Type, typename... Types>
inline void _func(const Value_Type& firstArg, const Types&... args) {
    ++value;
    _func(args...);
}

// 包装
template <typename... Types>
inline void func(const Types&... args) {
    _func(args...);
    cout << "value: " << value << endl;
}

}  // namespace Test

using namespace Test;
int main() {
    func(1, 2, 3, 4, 5);             // 5
    func("string", "fuck", 2, 1.2);  // 9

    return 0;
}

第二个例子:用c++模拟printf函数(简易版)

#include <iostream>
using namespace std;

namespace Print {
// 代码中抛出异常的部分先不管
// 用参数包重写printf函数 理解
inline void myprintf(const char* str) {
    while (*str) {
        if (*str == '%' && *(++str) != '%')  // 已经没有参数包了还有控制符号,不对劲,抛出异常
            throw runtime_error("invalid format string: missing arguments.");
        cout << *str++;
    }
}

template <typename Value_Type, typename... Types>
inline void myprintf(const char* str, const Value_Type& val, const Types&... args) {
    while (*str) {
        if (*str == '%' && *(++str) != '%') {  // 遇到控制符号了
            cout << val;
            myprintf(++str, args...);
            return;
        }
        cout << *str++;
    }
    throw logic_error("extra arguments provided to myprintf");
}
}  // namespace Print

using namespace Print;
int main() {
    myprintf("hello\n");

    int* pi = new int;
    // 但是这么模拟有一个很大的问题,就是控制符号我们没去管,但是介于只是一个简单的模拟,还是可以的
    myprintf("%d %s %p %f\n", 15, "This is Ace.", pi, 3.1415926535);
    delete pi;

    return 0;
}

打印tuple(这个例子非常巧妙)

image-20230505213553781

#include <iostream>
using namespace std;
#include <bitset>
#include <string>
#include <tuple>

namespace PRINT {

template <int index, int max, typename... Args>
struct Tuple_Print {
    inline static void print(ostream& os, const tuple<Args...>& t) {
        os << get<index>(t) << (index + 1 != max ? "," : "");  // 如果不是最后一个就是 , 号
        Tuple_Print<index + 1, max, Args...>::print(os, t);
    }
};

// 递归终点
template <int max, typename... Args>
struct Tuple_Print<max, max, Args...> {
    inline static void print(ostream& os, const tuple<Args...>& t) {}
};

}  // namespace PRINT

template <typename... Args>
inline ostream&
operator<<(ostream& os, const tuple<Args...>& t) {
    os << "[";
    PRINT::Tuple_Print<0, sizeof...(Args), Args...>::print(os, t);
    return os << "]";
}

int main() {
    cout << make_tuple(7.5, string("hello"), bitset<16>(377), 42) << endl;

    return 0;
}

第二讲:标准库

右值引用

记住:

  • 左值 != 左值引用
  • 右值 != 右值引用

Lvalue:只能出现在operator = 左边

Rvalue:只能出现再operator = 右边

临时对象是一个右值,右值不能出现在 = 号的左边,临时对象tmp一定被当作右值!!!

注意copy ctor和move ctor之间的区别:

image-20230514163001551

move():标准库提供的可以把左值变为右值的函数

Perfect Forwarding:在途中把Vtype(buf)(右值)交给Mystring的move ctor的时候会先经过insert函数在调用move ctor,这就有一个中间传递的过程,所以如何做到Perfect Forwarding是一个非常重要的事情,确保该传递的信息不能丢失

image-20230514170850534

Unperfect Forwarding

image-20230514171027632

Perfect Forwarding的具体实现:

image-20230516105734995

写一个 move aware class

image-20230516114646497

在 move ctor 当中,为什么要把原来的指针设为nullptr呢?(打断)

这是因为假如传入的右值对象是临时对象,临时对象的生命周期就只有这一句代码,执行完过后就会被释放,如果不打断,对于这里的string而言,就会调用析构函数把这个临时对象以及临时对象指向的区域给释放掉,因此就影响到了_data的部分,虽然这个临时对象今后不再用了,但是我们还是要把它与我们偷来的数据进行打断,并且配套的在析构函数的部分将其释放,否则会出现上面的问题

move ctor和move asgn的测试

MyString.h

#ifndef _MYSTRING_H_
#define _MYSTRING_H_

using namespace std;
#include <cstring>
#include <iostream>
#include <string>
// 写一个 move aware class
class Mystring {
public:
    static size_t DCtor;  // 累计 default-ctor呼叫次数
    static size_t Ctor;   // 累计 ctor呼叫次数
    static size_t CCtor;  // 累计 copy-ctor呼叫次数
    static size_t CAsgn;  // 累计 copy-asgn呼叫次数
    static size_t MCtor;  // 累计 move-ctor呼叫次数
    static size_t MAsgn;  // 累计 move-asgn呼叫次数
    static size_t Dtor;   // 累计 default-ctor呼叫次数
private:
    char* _data;
    size_t _len;

    void _init_data(const char* s) {
        _data = new char[_len + 1];
        memcpy(_data, s, _len);  // 这是一个深拷贝
        _data[_len] = '\0';
    }

public:
    // default-ctor
    Mystring() : _data(nullptr), _len(0) { ++DCtor; }

    // ctor
    Mystring(const char* p) : _len(strlen(p)) {
        ++Ctor;
        _init_data(p);
    }

    // copy-ctor
    Mystring(const Mystring& str) : _len(str._len) {
        ++CCtor;
        _init_data(str._data);
    }

    // copy-asgn
    Mystring& operator=(const Mystring& str) {
        ++CAsgn;
        // 自我赋值检查
        if (this != &str) {
            _len = str._len;
            _init_data(str._data);
        } else
            throw invalid_argument("cannot assign yourself.");
        return *this;
    }

    // move ctor, with noexcept
    Mystring(Mystring&& str) noexcept : _data(str._data), _len(str._len) {  // 指针相同表示指向同一块内存,就是一个偷的动作,是浅拷贝!!!
        // 完事之后将原来的str处理一下,能够传入右值引用都表示今后这个东西不用了
        // 所以不用了,但是也不要删除掉
        ++MCtor;
        str._len = 0;
        str._data = nullptr;  // 重要!!!
    }

    // move asgn, with noexcept
    Mystring& operator=(Mystring&& str) {
        ++MAsgn;
        // 自我赋值检查
        if (this != &str) {
            _data = str._data;
            _len = str._len;

            str._len = 0;
            str._data = nullptr;
        }
        return *this;
    }

    // dtor
    virtual ~Mystring() {
        ++DCtor;
        if (_data)
            delete _data;
    }

    // operator <
    bool operator<(const Mystring& rhs) const {  // 为了set
        return string(this->_data) < string(rhs._data);
    }

    // operator ==
    bool operator==(const Mystring& rhs) const {  // 为了set
        return string(this->_data) == string(rhs._data);
    }

    char* get() const { return _data; }
};

// 初始化静态变量
size_t Mystring::DCtor = 0;  // 累计 default-ctor呼叫次数
size_t Mystring::Ctor = 0;   // 累计 ctor呼叫次数
size_t Mystring::CCtor = 0;  // 累计 copy-ctor呼叫次数
size_t Mystring::CAsgn = 0;  // 累计 copy-asgn呼叫次数
size_t Mystring::MCtor = 0;  // 累计 move-ctor呼叫次数
size_t Mystring::MAsgn = 0;  // 累计 move-asgn呼叫次数
size_t Mystring::Dtor = 0;   // 累计 default-ctor呼叫次数

// 处理hashcode 放在std中和标准库合并
namespace std {
template <>
struct hash<Mystring> {
    size_t operator()(const Mystring& s) {
        return hash<string>()(string(s.get()));
    }
};
}  // namespace std

#endif

MyStrNoMove.h

#ifndef _MYSTRNOMOVE_H_
#define _MYSTRNOMOVE_H_

using namespace std;
#include <cstring>
#include <iostream>
#include <string>

class MyStrNoMove {
    // 拿掉move ctor和 move asgn
public:
    static size_t DCtor;  // 累计 default-ctor呼叫次数
    static size_t Ctor;   // 累计 ctor呼叫次数
    static size_t CCtor;  // 累计 copy-ctor呼叫次数
    static size_t CAsgn;  // 累计 copy-asgn呼叫次数
    static size_t MCtor;  // 累计 move-ctor呼叫次数
    static size_t MAsgn;  // 累计 move-asgn呼叫次数
    static size_t Dtor;   // 累计 default-ctor呼叫次数
private:
    char* _data;
    size_t _len;

    void _init_data(const char* s) {
        _data = new char[_len + 1];
        memcpy(_data, s, _len);  // 这是一个深拷贝
        _data[_len] = '\0';
    }

public:
    // default-ctor
    MyStrNoMove() : _data(nullptr), _len(0) { ++DCtor; }

    // ctor
    MyStrNoMove(const char* p) : _len(strlen(p)) {
        ++Ctor;
        _init_data(p);
    }

    // copy-ctor
    MyStrNoMove(const MyStrNoMove& str) : _len(str._len) {
        ++CCtor;
        _init_data(str._data);
    }

    // copy-asgn
    MyStrNoMove& operator=(const MyStrNoMove& str) {
        ++CAsgn;
        // 自我赋值检查
        if (this != &str) {
            _len = str._len;
            _init_data(str._data);
        } else
            throw invalid_argument("cannot assign yourself.");
        return *this;
    }

    // dtor
    virtual ~MyStrNoMove() {
        ++DCtor;
        if (_data)
            delete _data;
    }

    // operator <
    bool operator<(const MyStrNoMove& rhs) const {  // 为了set
        return string(this->_data) < string(rhs._data);
    }

    // operator ==
    bool operator==(const MyStrNoMove& rhs) const {  // 为了set
        return string(this->_data) == string(rhs._data);
    }

    char* get() const { return _data; }
};

// 初始化静态变量
size_t MyStrNoMove::DCtor = 0;  // 累计 default-ctor呼叫次数
size_t MyStrNoMove::Ctor = 0;   // 累计 ctor呼叫次数
size_t MyStrNoMove::CCtor = 0;  // 累计 copy-ctor呼叫次数
size_t MyStrNoMove::CAsgn = 0;  // 累计 copy-asgn呼叫次数
size_t MyStrNoMove::MCtor = 0;  // 累计 move-ctor呼叫次数
size_t MyStrNoMove::MAsgn = 0;  // 累计 move-asgn呼叫次数
size_t MyStrNoMove::Dtor = 0;   // 累计 default-ctor呼叫次数

// 处理hashcode 放在std中和标准库合并
namespace std {
template <>
struct hash<MyStrNoMove> {
    size_t operator()(const MyStrNoMove& s) {
        return hash<string>()(string(s.get()));
    }
};
}  // namespace std

#endif

test.h

#ifndef _TEST_H_
#define _TEST_H_

#include <ctime>
#include <deque>
#include <iostream>
#include <list>
#include <set>
#include <unordered_set>
#include <vector>
using namespace std;
#include "25_MyStrNoMove.h"
#include "25_Mystring.h"

namespace Test {
//--------------------------------------------------------
template <typename MyString>
void output_static_data(const MyString &str) {
    cout << typeid(str).name() << "--" << endl;
    cout << "CCtor= " << MyString::CCtor
         << " MCtor= " << MyString::MCtor
         << " CAsgn= " << MyString::CAsgn
         << " MAsgn= " << MyString::MAsgn
         << " Dtor= " << MyString::Dtor
         << " Ctor= " << MyString::Ctor
         << " DCtor= " << MyString::DCtor
         << endl;
}

// test_moveable
template <typename M, typename NM>
void test_moveable(M c1, NM c2, long &value) {
    char buf[10];

    // 测试 moveable
    cout << "\ntest, with moveable elements" << endl;
    typedef typename iterator_traits<typename M::iterator>::value_type V1type;
    clock_t timeStart = clock();
    for (long i = 0; i < value; ++i) {
        snprintf(buf, 10, "%d", rand());
        auto ite = c1.end();
        c1.insert(ite, V1type(buf));
    }
    cout << "construction, milli-seconds : " << double(clock() - timeStart) / 1000 << endl;
    cout << "size()= " << c1.size() << endl;
    output_static_data(*(c1.begin()));

    timeStart = clock();
    M c11(c1);
    cout << "copy, milli-seconds : " << double(clock() - timeStart) / 1000 << endl;

    timeStart = clock();
    M c12(std::move(c1));
    cout << "move copy, milli-seconds : " << double(clock() - timeStart) / 1000 << endl;

    timeStart = clock();
    c11.swap(c12);
    cout << "swap, milli-seconds : " << double(clock() - timeStart) / 1000 << endl;

    // 测试 non-moveable
    cout << "\ntest, with non-moveable elements" << endl;
    typedef typename iterator_traits<typename NM::iterator>::value_type V2type;
    timeStart = clock();
    for (long i = 0; i < value; ++i) {
        snprintf(buf, 10, "%d", rand());
        auto ite = c2.end();
        c2.insert(ite, V2type(buf));
    }

    cout << "construction, milli-seconds : " << double(clock() - timeStart) / 1000 << endl;
    cout << "size()= " << c2.size() << endl;
    output_static_data(*(c2.begin()));

    timeStart = clock();
    NM c21(c2);
    cout << "copy, milli-seconds : " << double(clock() - timeStart) / 1000 << endl;

    timeStart = clock();
    NM c22(std::move(c2));
    cout << "move copy, milli-seconds : " << double(clock() - timeStart) / 1000 << endl;

    timeStart = clock();
    c21.swap(c22);
    cout << "swap, milli-seconds : " << double(clock() - timeStart) / 1000 << endl;
}
//--------------------------------------------------------

// 将标识位 清0
void clear() {
    Mystring::DCtor = 0;
    Mystring::Ctor = 0;
    Mystring::CCtor = 0;
    Mystring::CAsgn = 0;
    Mystring::MCtor = 0;
    Mystring::MAsgn = 0;
    Mystring::Dtor = 0;

    MyStrNoMove::DCtor = 0;
    MyStrNoMove::Ctor = 0;
    MyStrNoMove::CCtor = 0;
    MyStrNoMove::CAsgn = 0;
    MyStrNoMove::MCtor = 0;
    MyStrNoMove::MAsgn = 0;
    MyStrNoMove::Dtor = 0;
}

// test_vector
void test_vector(long &value) {
    cout << "\ntest_vector().......... \n";
    test_moveable(vector<Mystring>(), vector<MyStrNoMove>(), value);
    cout << endl;
}

// test_list
void test_list(long &value) {
    cout << "\ntest_list().......... \n";
    test_moveable(list<Mystring>(), list<MyStrNoMove>(), value);
    cout << endl;
}

// test_deque
void test_deque(long &value) {
    cout << "\ntest_deque().......... \n";
    test_moveable(deque<Mystring>(), deque<MyStrNoMove>(), value);
    cout << endl;
}

// test_multiset
void test_multiset(long &value) {
    cout << "\ntest_multiset().......... \n";
    test_moveable(multiset<Mystring>(), multiset<MyStrNoMove>(), value);
    cout << endl;
}

// test_unordered_multiset
// void test_unordered_multiset(long &value) {
//     cout << "\ntest_unordered_multiset().......... \n";
//     test_moveable(unordered_multiset<Mystring>(), unordered_multiset<MyStrNoMove>(), value);
//     cout << endl;
// }
}  // namespace Test

#endif

main.cpp

#include <iostream>
using namespace std;
#include "25_MyStrNoMove.h"
#include "25_Mystring.h"
#include "25_test.h"

int main() {
    long value = 3 * 10e5;

    Test::test_vector(value);
    Test::clear();

    Test::test_list(value);
    Test::clear();

    Test::test_deque(value);
    Test::clear();

    Test::test_multiset(value);
    Test::clear();

    // Test::test_unordered_multiset(value);
    // Test::clear();

    return 0;
}

执行结果:

image-20230516165803938

适配器 Adapter 补充

X适配器:ostream_iterator

可以用来连接 cout

image-20230516203937375

#include <algorithm>  //std::copy
#include <iostream>   //std::cout
#include <iterator>   //std::ostream_iterator
#include <vector>     //std::vector

int main() {
    std::vector<int> v;
    for (int i = 0; i < 10; ++i) v.push_back(i * 10);

    std::ostream_iterator<int> out_it(std::cout, ",");
    std::copy(v.begin(), v.end(), out_it);
    std::cout << std::endl;

    return 0;
}
istream_iterator

可以用来连接 cin

image-20230516205607537

#include <iostream>  //std::cin std::cout
#include <iterator>  //std::istream_iterator

int main() {
    double value1, value2;
    std::cout << "Please,insert two values: ";
    std::istream_iterator<double> eos;             // end-of-stream iterator
    std::istream_iterator<double> iter(std::cin);  // stdin iterator

    if (iter != eos)
        value1 = *iter;
    ++iter;
    if (iter != eos)
        value2 = *iter;

    std::cout << value1 << " * " << value2 << " == " << value1 * value2 << std::endl;

    return 0;
}
type traits

image-20230516212207244

以前的版本由于标准的限制,最好写自定义类的时候也要带上这个 __type_traits<>

C++2.0 新版本

trivial 不重要的
POD plain old data 平淡的旧风格的,指的就是C风格的,也就是只有成员变量没有成员方法

image-20230516221129783

image-20230516221225536

type traits 测试

image-20230516221737119

type_traits 实现 is_void(了解)

image-20230517141545661

#include <iostream>
using namespace std;
#include <type_traits>

// my_isVoid 简单版本
template <class Value_Type>
struct my_isVoid : public false_type {};

// 特化版本
template <>
struct my_isVoid<void> : public true_type {};

int main() {
    cout << my_isVoid<int>::value << endl;
    cout << my_isVoid<void>::value << endl;

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DavidingPlus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值