四、可变参数模版
4.4 emplace系列接口

通过前面的学习,我们知道emplace_back和push_back的区别其实不是很大,真正的区别就是:

  • emplace_bakc是一个可变参数模版,而push_back只是一个普通的函数

emplace_back可以传参数包进行构造,而push_back不能传参数包,要么是传插入的对象,或者是进行隐式类型转换

ok,当我们了解了这些,我们就来实现一个emplace_back:

4.5 emplace和emplace_back在list中的模拟实现

代码语言:javascript

AI代码解释

template<class ...Args>
void emplace_back(Args... args)
{
	emplace(end(), forward<Args>(args)...);
}
template<class ...Args>
iterator emplace(iterator pos,Args&&... args)
{
	Node* cur = pos._node;
	Node* newnode = new Node(forward<Args>(args)...);
	Node* prev = cur->_prev;
	// prev newnode cur
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;
	return iterator(newnode);
}

ok,当我们实现了emplace_back和emplace接口后,我们就要实现相应的可变模板参数版本的构造节点的代码:

代码语言:javascript

AI代码解释

template <class... Args>
ListNode(Args&&... args)
	: _next(nullptr)
	, _prev(nullptr)
	, _data(std::forward<Args>(args)...)
{}

ok,这样改完之后,我们就可以使用emplace_back进行尾插操作。

但是,当我们加上emplace_back后,push_back就不能使用万能引用版本的尾插,为什么?

  1. 库中并没有把push_back写成泛型化,因为有了emplace_back就没有必要将push_back写成泛型化
  2. 要兼容以前的代码,我们只能写一个左值版本和一个右值版本的push_back代码

总结:有了emplace_back,就不需要这个万能引用版的push_back,直接使用左值版本和右值版本的push_back

emplace_back总体而言是更高效,推荐以后使用emplace系列替代insert和push系列

  • 除此之外:emplace_back和push_back的用法是不能混着的~

五、C++11中类的新功能
5.1 默认的移动构造和移动赋值

原来的C++类中,有6个默认成员函数:构造、析构、拷贝构造、赋值重载、取地址重载、const取地址重载,最重要的是前4个,后2个用处不大

所谓默认成员函数,就是我们不显示写,编译器会默认生成一个。

C++11中新增了两个默认成员函数——

  • 移动构造函数
  • 移动赋值函数

通过前面的学习,我们知道,对于默认成员函数的学习,我们通常进行一下几点:

  • 我们不显示写,编译器默认生成的行为是什么?满不满足我们的需求?如果不满足我们的需求,我们该怎么写?

ok,那我们先来看移动构造函数——

5.1.1 移动构造函数

如果你没有自己实现移动构造函数,并且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,(换句话来说:就是没有实现移动构造、析构函数、拷贝构造、拷贝赋值重载),那么编译器会自动生成一个默认移动构造(若实现了其中一个,编译器都不会生成默认移动构造)

默认生成的移动构造函数:

  • 对于内置类型成员会执行逐成员按字节拷贝(完成值拷贝);
  • 对于自定义类型成员,则需要看这个自定义类型成员中是否实现移动构造,如果实现了移动构造,则调用移动构造;如果没有实现移动构造,则调用拷贝构造

这就说明:自身需要深拷贝处理的类型,需要自己实现移动构造

通过上面,我们也能看出,移动构造函数试用于复合类型——

就比如,我们之前的MyQueue——

代码语言:javascript

AI代码解释

class MyQueue
{
	std::stack _pushSt;
	std::stack _popSt;
};

移动构造函数对于MyQueue就很有意义,MyQueue中没有写析构函数、拷贝构造、赋值重载以及移动构造,MyQueue中就会默认生成一个移动构造函数。

这个默认生成的移动构造函数:

  • 对于内置类型成员,完成逐字节的拷贝;
  • 对于自定义类型成员,会去调用栈中的移动构造,如果栈中没有实现移动构造,就去调用栈中的拷贝构造

移动构造和拷贝构造的逻辑相似!!!

代码语言:javascript

AI代码解释

namespace carrot
{
    class string
    {
    public:
        typedef char* iterator;
        typedef const char* const_iterator;

        iterator begin()
        {
            return _str;
        }
        iterator end()
        {
            return _str + _size;
        }

        const_iterator begin() const
        {
            return _str;
        }

        const_iterator end() const
        {
            return _str + _size;
        }

        string(const char* str = "")
            :_size(strlen(str))
            , _capacity(_size)
        {
            cout << "string(char* str)-构造" << endl;
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }

        void swap(string& s)
        {
            ::swap(_str, s._str);
            ::swap(_size, s._size);
            ::swap(_capacity, s._capacity);
        }

        // 拷贝构造
        string(const string& s)
        {
            cout << "string(const string& s) -- 拷贝构造" << endl;

            reserve(s._capacity);
            for (auto ch : s)
            {
                push_back(ch);
            }
        }

        // 移动构造
        string(string&& s)
        {
            cout << "string(string&& s) -- 移动构造" << endl;
            swap(s);
        }

        string& operator=(const string& s)
        {
            cout << "string& operator=(const string& s) -- 拷贝赋值" << endl;
            if (this != &s)
            {
                _str[0] = '\0';
                _size = 0;

                reserve(s._capacity);
                for (auto ch : s)
                {
                    push_back(ch);
                }
            }

            return *this;
        }

        // 移动赋值
        string& operator=(string&& s)
        {
            cout << "string& operator=(string&& s) -- 移动赋值" << endl;
            swap(s);
            return *this;
        }

        ~string()
        {
            //cout << "~string() -- 析构" << endl;
            delete[] _str;
            _str = nullptr;
        }

        char& operator[](size_t pos)
        {
            assert(pos < _size);
            return _str[pos];
        }

        void reserve(size_t n)
        {
            if (n > _capacity)
            {
                char* tmp = new char[n + 1];
                if (_str)
                {
                    strcpy(tmp, _str);
                    delete[] _str;
                }
                _str = tmp;
                _capacity = n;
            }
        }

        void push_back(char ch)
        {
            if (_size >= _capacity)
            {
                size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newcapacity);
            }

            _str[_size] = ch;
            ++_size;
            _str[_size] = '\0';
        }

        string& operator+=(char ch)
        {
            push_back(ch);
            return *this;
        }

        const char* c_str() const
        {
            return _str;
        }

        size_t size() const
        {
            return _size;
        }
    private:
        char* _str = nullptr;
        size_t _size = 0;
        size_t _capacity = 0;
    };
}
class Person
{
public:
    Person(const char* name="张三",int age=18)
        :_name(name)
        ,_age(age)
    {}
private:
    bit::string _name;
    int _age;
};
int main()
{
    Person s1;//构造
    Person s2 = s1;//拷贝构造
    Person s3 = std::move(s2);//右值,移动构造
    return 0;
}

ok,前两个还是比较容易理解的,我们直接看第三个,第三个就比较有意思——

运行结果——

那我们现在来验证一下,当自定义类型成员中没有移动构造函数,会不会调用自定义类型中的拷贝构造函数——

  • 显示写移动构造函数的写法和拷贝构造的写法类似,拷贝构造会开新空间,而移动构造直接转移资源——

代码语言:javascript

AI代码解释

// 拷贝构造
string(const string& s)
{
    cout << "string(const string& s) -- 拷贝构造" << endl;

    reserve(s._capacity);
    for (auto ch : s)
    {
        push_back(ch);
    }
}

// 移动构造
string(string&& s)
{
    cout << "string(string&& s) -- 移动构造" << endl;
    swap(s);
}

注意:通过上面的代码,我们也能看出,不要轻易的对一个值进行move操作!!!

5.1.2 移动赋值函数

默认移动赋值和上面的移动构造完全类似

如果你没有自己实现移动构造函数,并且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,(换句话来说:就是没有实现移动构造、析构函数、拷贝构造、拷贝赋值重载),那么编译器会自动生成一个默认移动赋值(若实现了其中一个,编译器都不会生成默认移动赋值)

默认生成的移动赋值函数:

  • 对于内置类型成员会执行逐成员按字节拷贝(完成值拷贝);
  • 对于自定义类型成员,则需要看这个自定义类型成员中是否实现移动赋值,如果实现了移动赋值,则调用移动赋值;如果没有实现移动赋值,则调用赋值重载

ok,我们通过代码来看一下——

代码语言:javascript

AI代码解释

class Person
{
public:
    Person(const char* name="张三",int age=18)
        :_name(name)
        ,_age(age)
    {}
private:
    carrot::string _name;
    int _age;
};
int main()
{
    Person s1;
    Person s2 = s1;
    Person s4("李四", 20);
    s4 = std:: move(s2);
    return 0;
}

运行一下——

那我们现在来验证一下,当自定义类型成员中没有移动赋值函数,会不会调用自定义类型中的拷贝构造函数——

ok,移动赋值函数这里还有一个特殊的地方——

我们通过调试来看一下——

移动赋值函数的写法和赋值重载的写法相似——

代码语言:javascript

AI代码解释

//赋值重载
string& operator=(const string& s)
{
    cout << "string& operator=(const string& s) -- 拷贝赋值" << endl;
    if (this != &s)
    {
        _str[0] = '\0';
        _size = 0;

        reserve(s._capacity);
        for (auto ch : s)
        {
            push_back(ch);
        }
    }

    return *this;
}

// 移动赋值
string& operator=(string&& s)
{
    cout << "string& operator=(string&& s) -- 移动赋值" << endl;
    swap(s);
    return *this;
}

如果你自己提供了移动构造或者移动赋值,编译器就不会自动提供默认的移动构造或者移动赋值。

如果显示提供析构、拷贝构造、赋值重载中的任意一个,编译器就默认生不成移动构造和移动赋值了,就会去调用拷贝构造或者拷贝赋值(我们不写拷贝构造或者拷贝赋值,编译器会生成默认的拷贝构造),左值可以使用,右值也可以使用


 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐