拷贝控制操作

拷贝控制操作:拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符、析构函数。

1. 拷贝初始化发生的时机

1.使用=定义变量;
2.将一个对象作为实参传递给一个非引用类型的形参;
3.从一个返回类型为非引用类型的函数返回一个对象;
4.用花括号列表初始化一个数组中的元素或一个聚合类中的成员。

2. 构造、析构过程

在构造函数中,成员的初始化是在函数体执行之前完成的,且按照它们在类中出现的顺序进行初始化;
在析构函数中,首先执行函数体,然后销毁成员,且成员按照初始化顺序的逆序销毁。

2.1 什么时候调用析构函数

无论何时一个对象被销毁,就会自动调用析构函数:
1.变量在离开作用域时会被销毁;
2.当一个对象被销毁时,其成员被销毁;
3.容器(无论时标准库容器还是数组)被销毁时,其元素被销毁;
4.对于动态分配的对象,当对指向它的指针应用delete运算符时被销毁;
5.对于临时对象,当创建它的完整表达式结束时被销毁。

3. 什么原因会导致类的拷贝控制成员被定义为删除函数

  1. 如果类的某个成员的析构函数是删除的或不可访问的(例如,是private的),则类的合成析构函数被定义为删除的。
  2. 如果类的某个成员的拷贝构造函数是删除的或不可访问的,则类的合成拷贝构造函数被定义为删除的。如果类的某个成员的析构函数是删除的或不可访问的,则类合成的拷贝构造函数也被定义为删除的。
  3. 如果类的某个成员的拷贝赋值运算符是删除的或不可访问的,或是类有一个const的或引用成员,则类的合成拷贝赋值运算符被定义为删除的。
  4. 如果类的某个成员的析构函数是删除的或不可访问的,或是类有一个引用成员,它没有类内初始化器,或是类有一个const成员,它没有类内初始化器且其类型未显式定义默认构造函数,则该类的默认构造函数被定义为删除的。

本质上,这些规则的含义是:如果一个类有数据成员不能默认构造、拷贝、复制或销毁,则对应的成员函数将被定义为删除的。

4.拷贝赋值运算符

拷贝赋值运算符的深拷贝实现
拷贝赋值运算符通常组合了析构函数和构造函数的操作。类似析构函数,赋值操作会销毁左侧运算符对象的资源,类似拷贝构造函数赋值操作会从右侧运算对象拷贝数据。需要注意操作的顺序,保障可以自赋值

拷贝赋值运算符的浅拷贝(引用计数)实现
当拷贝一个对象时,副本和原对象使用相同的底层数据,改变副本也会改变原对象,反之亦然。

ellipse
图1 引用计数的工作方式

引用计数的计数器应该保存在动态内存中

5.swap操作

对于类类型变量的交换操作,如果使用标准库版本的swap会进行一次拷贝构造,两次拷贝赋值运算符的调用。
可以自定义swap操作代替标准库的版本从而节省一些额外开支。

调用标准库版本的std::swap示例:

#pragma once

#include <iostream>
#include <string>

namespace test_swap
{
    class HasPtr{
        friend void swap(HasPtr&, HasPtr&);
    public:
        explicit HasPtr(const std::string& s = std::string(""), int i=0): ps_(new std::string(s)), i_(i){
            
        }
        
        HasPtr(const HasPtr& p): ps_(new std::string(*p.ps_)), i_(p.i_){
            std::cout << "Using copy-ctor.\n";
        }
        
        HasPtr& operator=(const HasPtr& p){
            std::cout << "Using copy-assign operator.\n";
            if(this == &p){return *this;}
            
            delete this->ps_;
            ps_ = new std::string(*p.ps_);
            i_ = p.i_;
            
            return *this;
        }
        
        ~HasPtr(){
            delete ps_;
        }
        
        void showContent(){
            std::cout << i_ << ", " << *ps_ << std::endl;
        }
        
    private:
        std::string *ps_;
        int i_;        
    };
    
    inline void swap(HasPtr& lhs, HasPtr& rhs) {
        std::cout << "HasPtr, Using self-define swap." << std::endl;
        using std::swap;
        swap(lhs.ps_, rhs.ps_);
        swap(lhs.i_, rhs.i_);
    }
    
    class Foo{
    friend void swap(Foo&, Foo&);
    public:    
        Foo() = default;
        Foo(const std::string &s, int i): hp_(s, i){}
        
        void show(){
            hp_.showContent();
        }
    private:
        HasPtr hp_;
    };
    
#if 1
    inline void swap(Foo& a, Foo& b) {
        std::cout << "Foo, Using std::swap." << std::endl;
        
        std::swap(a.hp_, b.hp_);
    }
#endif
    
#if 0
    inline void swap(Foo& a, Foo& b) {
        std::cout << "Foo, Using self-define swap." << std::endl;
        using std::swap;
        swap(a.hp_, b.hp_);
    }
#endif   

    int main()
    {
        std::cout << "test_swap......." << std::endl;

        // HasPtr hp1(std::string("Hello swap1."), 1);
        // HasPtr hp2(std::string("Hello swap2."), 2);
        // hp1.showContent();
        // hp2.showContent();
        // std::cout << "+++++++++\n";
        // swap(hp1, hp2);
        // hp1.showContent();
        // hp2.showContent();
        // 
        // std::cout << "+++++++++\n";

        Foo foo1(std::string("foo1"), 3);
        Foo foo2(std::string("foo2"), 4);
        foo1.show();
        foo2.show();
        swap(foo1, foo2);
        foo1.show();
        foo2.show();

        std::cout << "test_swap pass\n----------------------------" << std::endl;

        return 0;
    }
}

输出:

test_swap.......
3, foo1
4, foo2
Foo, Using std::swap.
Using copy-ctor.
Using copy-assign operator.
Using copy-assign operator.
4, foo2
3, foo1
test_swap pass
----------------------------

调用自定义版本的std::swap示例:

test_swap.......
3, foo1
4, foo2
Foo, Using self-define swap.
HasPtr, Using self-define swap.
4, foo2
3, foo1
test_swap pass
----------------------------

6.对象移动

右值引用(rvalue reference)是必须绑定到右值的引用,通过&&而不是&来获得右值引用。
右值引用只能绑定到一个将要销毁的对象。因此,可以将一个右值引用的资源“移动”到另一个对象中。

左值引用:不能将其绑定到要求转换的表达式、字面常量、返回右值的表达式。
右值引用:有着完全相反的绑定特性,可以将一个右值引用绑定到上述类型的表达式上,但不能将一个右值引用直接绑定到一个左值上。

6.1标准库move函数

int i = 42;
int &r =  i;              // 正确,r引用i
int &&rr = i;             // 错误,不能将右值引用绑定到左值
int &r2 = i * 32;         // 错误,i*32是右值
const int &r3 = i * 32;   // 正确,将const引用绑定到右值
int &&rr2 = i * 42;       // 正确,将rr2绑定到乘法结果上

变量是左值,因此不能将一个右值引用直接绑定到变量上,即使该变量是右值引用类型也不行。
其实,区分是左值还是右值,就看能不能取其地址即可。

例如:

int &&rr1 = 43;    // 正确,字面常量是右值
int &&rr2 = rr1;   // 错误,表达式rr1是左值,左值,左值

虽然不能将一个右值引用直接绑定到一个左值上,但可以通过标准库函数std::move来获得绑定到左值上的右值引用。

int &&rr1 = 43;               // 正确,字面常量是右值
int &&rr3 = std::move(rr1);   // 正确

需要注意的是,对于rr1,除了赋值、销毁之外不能再使用它,因为再调用std::move之后不能对移动后的源对象的值有任何假设。

6.2移动操作示例

需要注意的是,在移动操作之后,移动后源对象必须保持有效的(可赋新值、可安全地使用)、可析构的状态,但不能对其值有任何假设。

#include <iostream>
#include <string>
#include <utility>

namespace test_move
{
	class Foo{
    public:
		Foo(const std::string& str=std::string("init value"), int i = -1): str_(new std::string(str)), i_(new int(i)){
			std::cout << "ctor called." << std::endl;
		}
		
		Foo(const Foo& f): str_(new std::string(*f.str_)), i_(new int(*f.i_)){
			std::cout << "copy-ctor called." << std::endl;
		}
		
		Foo& operator=(const Foo& f){
            std::cout << "copy-assign-operator called." << std::endl;

			if(this == &f){
				std::cout << "self-assign" << std::endl;
				return *this;
			}
			
			delete str_;
			delete i_;
			
			str_ = new std::string(*f.str_);
			i_ = new int(*f.i_);
			
			return *this;
		}
		
		// 不抛出异常的移动构造函数和移动赋值运算符必须标记为noexcept
		Foo(Foo&& f) noexcept: str_(f.str_), i_(f.i_){
			std::cout << "move-ctor called." << std::endl;
			
			f.str_ = nullptr;
			f.i_ = nullptr;
		}
		
		Foo& operator=(Foo&& f) noexcept{
			std::cout << "move-assign-operator called." << std::endl;
			
            // 如果右侧和左侧运算对象是相同的,不需要做任何事情;否则释放左侧运算对象所使用的内存,并接管给定对象的内存			
			if(this != &f){
				delete str_;
				delete i_;
				
				str_ = f.str_;
				i_ = f.i_;
			}
			
			return *this;
		}
		
		~Foo(){
			std::cout << "dtor called." << std::endl;
			delete str_; str_ = nullptr;
			delete i_; i_ = nullptr;
		}
		
	private:
		std::string *str_;
		int *i_;
	};

	auto main() -> int
	{
		std::cout << "Testing move......" << std::endl;

		Foo foo1(std::string("foo1"), 2);
		Foo foo2(foo1);
		Foo foo3(std::move(foo2));
		foo2 = foo1;
		foo1 = std::move(foo1);
		

		std::cout << "------------------------------" << std::endl;

		return 0;
	}
}

输出:

Testing move......
ctor called.
copy-ctor called.
move-ctor called.
copy-assign-operator called.
move-assign-operator called.
------------------------------
dtor called.
dtor called.
dtor called.

6.3编译器不会为某些类合成移动操作

与拷贝操作不同,编译器根本不会为某些类合成移动操作。特别是,如果一个类定义了自己的拷贝构造函数、拷贝赋值运算符或者析构函数,编译器就不会为它合成移动构造函数和移动赋值运算符了。
如果一个类没有移动操作,通过正常的函数匹配,类会使用对应的拷贝操作来代替移动操作。

同时,如果类定义了一个移动构造函数和/或一个移动赋值运算符,则该类的合成拷贝构造函数和拷贝赋值运算符会被定义为删除的。

#include <iostream>
#include <string>
#include <utility>

namespace test_move
{
	class Foo{
    public:
		Foo(const std::string& str=std::string("init value"), int i = -1): str_(new std::string(str)), i_(new int(i)){
			std::cout << "ctor called." << std::endl;
		}
		
		Foo(const Foo& f): str_(new std::string(*f.str_)), i_(new int(*f.i_)){
			std::cout << "copy-ctor called." << std::endl;
		}
		
		Foo& operator=(const Foo& f){
            std::cout << "copy-assign-operator called." << std::endl;

			if(this == &f){
				std::cout << "self-assign" << std::endl;
				return *this;
			}
			
			delete str_;
			delete i_;
			
			str_ = new std::string(*f.str_);
			i_ = new int(*f.i_);
			
			return *this;
		}
		
		// 不抛出异常的移动构造函数和移动赋值运算符必须标记为noexcept
		// Foo(Foo&& f) noexcept: str_(f.str_), i_(f.i_){
		// 	std::cout << "move-ctor called." << std::endl;
		// 	
		// 	f.str_ = nullptr;
		// 	f.i_ = nullptr;
		// }
		// 
		// Foo& operator=(Foo&& f) noexcept{
		// 	std::cout << "move-assign-operator called." << std::endl;
		// 	
        //     // 如果右侧和左侧运算对象是相同的,不需要做任何事情;否则释放左侧运算对象所使用的内存,并接管给定对象的内存			
		// 	if(this != &f){
		// 		delete str_;
		// 		delete i_;
		// 		
		// 		str_ = f.str_;
		// 		i_ = f.i_;
		// 	}
		// 	
		// 	return *this;
		// }
		
		~Foo(){
			std::cout << "dtor called." << std::endl;
			delete str_; str_ = nullptr;
			delete i_; i_ = nullptr;
		}
		
	private:
		std::string *str_;
		int *i_;
	};

	auto main() -> int
	{
		std::cout << "Testing move......" << std::endl;

		Foo foo1(std::string("foo1"), 2);
		Foo foo2(foo1);
		Foo foo3(std::move(foo2));   // 调用拷贝构造
		foo2 = foo1;
		foo1 = std::move(foo1);      // 调用拷贝赋值
		

		std::cout << "------------------------------" << std::endl;

		return 0;
	}
}

输出:

Testing move......
ctor called.
copy-ctor called.
copy-ctor called.
copy-assign-operator called.
copy-assign-operator called.
self-assign
------------------------------
dtor called.
dtor called.
dtor called.

6.4编译器合成移动构造函数和移动赋值运算符的条件

只有当一个类没有定义任何自己版本的拷贝控制成员,且它的所有非static数据成员都能移动构造或移动赋值时,编译器才会为它合成移动构造函数或移动赋值运算符。

struct X {
	int i_;          // 内置类型可以移动
	std::string s_;  // string定义了自己的移动操作
};

struct hasX {
	X mem_;         // X有合成的移动操作
};

X x;
X x2 = std::move(x);       // 使用合成的移动构造函数
hasX hx;
hasX hx2 = std::move(hx);  // 使用合成的移动构造函数
ellipse
图2 移动操作被定义为删除的条件

定义了一个移动构造函数或移动赋值运算符的类必须也定义自己的拷贝操作,否则,这些成员默认地被定义为删除的。

6.5 移动右值,拷贝左值

如果一个类既有移动构造函数,也有拷贝构造函数,编译器使用普通的函数匹配规则来确定使用哪个构造函数;赋值操作也是类似。

#pragma once

#include <iostream>
#include <string>

namespace test_move
{
    class Foo {
    public:
        Foo() = default;
        Foo(std::string str, int i): str_(new std::string(str)), i_(new int(i)){
            std::cout << "ctor with parameter called.\n";
        }
        Foo(const Foo& f):str_(new std::string(*f.str_)), i_(new int(*f.i_)){
            std::cout << "copy-ctor called.\n";
        }
        Foo& operator=(const Foo& f){
            std::cout << "copy-assign called.\n";
            
            if(this != &f){
                delete this->str_;
                delete this->i_;
                
                str_ = new std::string(*f.str_);
                i_ = new int(*f.i_);
            }
            
            return *this;
        }
        Foo(Foo&& f):str_(f.str_), i_(f.i_){
            std::cout << "move-ctor called.\n";
            
            f.str_ = nullptr;
            f.i_ = nullptr;
        }
        Foo& operator=(Foo&& f){
            std::cout << "move-assign called.\n";
            
            str_ = f.str_;
            i_ = f.i_;
            
            f.str_ = nullptr;
            f.i_ = nullptr;
            
            return *this;
        }
        
        virtual ~Foo(){
            std::cout << "dtor called.\n";
            
            delete str_;
            delete i_;
            
            str_ = nullptr;
            i_ = nullptr;
        }
        
        void printFoo(){
            if(str_ && i_){
                std::cout << *str_ << ", " << *i_ << std::endl;
            }
        }
        
    private:
        std::string *str_;
        int *i_;
    };
    
    Foo getFoo(){
        return Foo(std::string("get"), 0);
    }
 
    int main()
    {
        std::cout << "test_move......." << std::endl;

        Foo f1(std::string("f1"), 1), f2(std::string("f2"), 2);
        f1.printFoo();
        f2.printFoo();
        
        f1 = f2;
        f1.printFoo();
        f2.printFoo();
        
        f2 = getFoo();  // f2是函数返回的结果,此表达式是右值,移动赋值运算符是精确匹配(Foo&),而拷贝赋值运算符需要进行一次到const的转换
        f1.printFoo();
        f2.printFoo();
        
        std::cout << "test_move pass\n----------------------------" << std::endl;

        return 0;
    }
}

输出:

test_move.......
ctor with parameter called.
ctor with parameter called.
f1, 1
f2, 2
copy-assign called.
f2, 2
f2, 2
ctor with parameter called.
move-assign called.
dtor called.
f2, 2
get, 0
test_move pass
----------------------------
dtor called.
dtor called.

6.6 如果没有移动构造函数,右值也被拷贝

如果一个类有一个拷贝构造函数但未定义移动构造函数,此时编译器不会合成移动构造函数。如果一个类没有移动构造函数,函数匹配规则保证该类型的对象会被拷贝,即使试图通过调用move来移动它们时也是如此。

#pragma once

#include <iostream>
#include <string>

namespace test_move
{
    class Foo {
    public:
        Foo() = default;
        Foo(std::string str, int i): str_(new std::string(str)), i_(new int(i)){
            std::cout << "ctor with parameter called.\n";
        }
        Foo(const Foo& f):str_(new std::string(*f.str_)), i_(new int(*f.i_)){
            std::cout << "copy-ctor called.\n";
        }
        Foo& operator=(const Foo& f){
            std::cout << "copy-assign called.\n";
            
            if(this != &f){
                delete this->str_;
                delete this->i_;
                
                str_ = new std::string(*f.str_);
                i_ = new int(*f.i_);
            }
            
            return *this;
        }
        // Foo(Foo&& f):str_(f.str_), i_(f.i_){
        //     std::cout << "move-ctor called.\n";
        //     
        //     f.str_ = nullptr;
        //     f.i_ = nullptr;
        // }
        // Foo& operator=(Foo&& f){
        //     std::cout << "move-assign called.\n";
        //     
        //     str_ = f.str_;
        //     i_ = f.i_;
        //     
        //     f.str_ = nullptr;
        //     f.i_ = nullptr;
        //     
        //     return *this;
        // }
        
        virtual ~Foo(){
            std::cout << "dtor called.\n";
            
            delete str_;
            delete i_;
            
            str_ = nullptr;
            i_ = nullptr;
        }
        
        void printFoo(){
            if(str_ && i_){
                std::cout << *str_ << ", " << *i_ << std::endl;
            }
        }
        
    private:
        std::string *str_;
        int *i_;
    };
    
    Foo getFoo(){
        return Foo(std::string("get"), 0);
    }
 
    int main()
    {
        std::cout << "test_move......." << std::endl;

        Foo f1(std::string("f1"), 1);  // ctor with parameter called.
        Foo f2(std::move(f1));         // copy-ctor called.
        f1.printFoo();
        f2.printFoo();
        
        f2 = std::move(f1);            // copy-assign called.
        f1.printFoo();
        f2.printFoo();        
        
        std::cout << "test_move pass\n----------------------------" << std::endl;

        return 0;
    }
}

输出:

test_move.......
ctor with parameter called.
copy-ctor called.
f1, 1
f1, 1
copy-assign called.
f1, 1
f1, 1
test_move pass
----------------------------
dtor called.
dtor called.

6.7 调用移动还是拷贝?

区分移动和拷贝的重载函数通常有一个版本接受一个const T&,而另一个版本接受一个T&&

#pragma once

#include <iostream>
#include <string>

namespace test_move
{
    void getFoo(std::string && str){
        std::cout << "rvale-version called: " << str << std::endl;
    }

    void getFoo(const std::string &str){
        std::cout << "lvale-version called: " << str << std::endl;
    }
    
    int main()
    {
        std::cout << "test_move......." << std::endl;

        getFoo("0123456789");
        
        std::string str("abcde");
        getFoo(str);
      
        std::cout << "test_move pass\n----------------------------" << std::endl;

        return 0;
    }
}

输出:

test_move.......
rvale-version called: 0123456789
lvale-version called: abcde
test_move pass
----------------------------

6.8 右值和左值引用成员函数

通常,不管对象时一个左值还是一个右值都可以在该对象上调用成员函数。
例如可以对两个std::string相加的结果(右值)进行赋值。

std::string s1("s1"), s2("s2");
auto found = (s1 + s2).find('s');
s1 + s2 = "Amazing!";

可以使用引用限定符(reference qualifier)来强制左侧运算符对象(即this指向的对象)是一个左值还是右值。引用限定符可以是&&&,分别指出this可以指向一个左值或右值。
类似const限定符,引用限定符只能用于(非static)成员函数,且必须同时出现在函数的声明和定义中。

#pragma once

#include <iostream>
#include <string>

namespace test_move
{
    class Foo{
    public:
    Foo(int i = 0):i_(i){
        std::cout << "ctor called.\n";
    }
    Foo& operator=(const Foo& f)&;
    
    Foo& operator=(const Foo& f)&&;
    
    Foo& retLvaue(){
        return *this;
    }
    
    Foo retRvaue(){
        return *this;
    }
    
    virtual ~Foo() = default;
    
    private:        
        int i_;
    };
    
    Foo& Foo::operator=(const Foo& f)& {
        std::cout << "Only assign to lvalue\n";
        this->i_ = f.i_;
        
        return *this;
    }
    
    Foo& Foo::operator=(const Foo& f)&& {
        std::cout << "Only assign to rvalue\n";
        this->i_ = f.i_;
        
        return *this;
    }
    
    int main()
    {
        std::cout << "test_move......." << std::endl;

        Foo f1(1), f2(2);

        f1 = f2;               // Only assign to lvalue
        
        f1.retLvaue() = f2;    // Only assign to lvalue
        
        f1.retRvaue() = f2;    // Only assign to rvalue
        
        f1 = f2.retLvaue();    // Only assign to lvalue
        f1 = f2.retRvaue();    // Only assign to lvalue
      
        std::string s1("s1"), s2("s2");
        auto found = (s1 + s2).find('s');
        s1 + s2 = "Amazing!";
        
        std::cout << "test_move pass\n----------------------------" << std::endl;

        return 0;
    }
}

输出:

test_move.......
ctor called.
ctor called.
Only assign to lvalue
Only assign to lvalue
Only assign to rvalue
Only assign to lvalue
Only assign to lvalue
test_move pass
----------------------------

一个函数可以同时使用const和引用限定,在此情况下,引用限定符必须跟随在const限定符之后。

#pragma once

#include <iostream>
#include <string>

namespace test_move
{
    class Bar{
    public:
        Bar() = default;
        virtual ~Bar() = default;
        
        std::string& retLval() const &;
        
        std::string retLval() const &&;
        
    private:
        std::string str_="default val";
    };
    
    auto main()-> void{
        std::cout << "test_move......." << std::endl;
                
        std::cout << "test_move pass\n----------------------------" << std::endl;        
    }
}

6.9 重载和引用函数

如果一个成员函数有引用限定符,则具有相同参数列表的所有版本都必须有引用限定符。

当定义const成员函数时,可以定义两个版本,唯一的差别是一个版本有const限定而另一个没有。
引用限定的函数则不一样,如果定义两个或两个以上具有相同名字和相同参数列表的成员函数,就必须对所有函数都加上引用限定符,或者所有都不加。

#pragma once

#include <iostream>

namespace test_move
{
    class Foo{
    public:
        Foo() = default;
        virtual ~Foo() = default;
        
        Foo sorted() &&;
        //Foo sorted() const; // error: 'test_move::Foo test_move::Foo::sorted() const' cannot be overloaded with 'test_move::Foo test_move::Foo::sorted() &&'
        
        Foo func();
        Foo func() const;
    };
    
    auto main()-> void{
        std::cout << "test_move......." << std::endl;
                
        std::cout << "test_move pass\n----------------------------" << std::endl;        
    }
}

7. 再谈移动

左值:可取地址有名字,是个指向某内存空间的表达式。
右值:不可取地址没名字,非左值者皆为右值,是个临时值,表达式结束后右值就没意义了。
如果右值引用有名字则为左值;如果右值引用没名字则为右值。

当右值出现于operator=的右侧,我们认为对其资源进行偷取/搬移(move)而非拷贝(copy)是合理的,那么:

  1. 必须有语法在调用端告诉编译器,这个时右值;
  2. 必须有语法在被调用端写出一个专门处理右值的move函数。

天然右值:临时对象
人工右值:std::move

int foo(){return 3;}
int x = foo();   // right
int *p = &foo(); // error
foo() = 7;       // error, foo()是天然右值

7.1 move-aware class示例

#pragma once

#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <deque>
#include <string>
#include <ctime>

namespace test_move
{
	class MyString
	{
	public:
		MyString():len_(0), data_(nullptr)
		{
		    ++sDCtor_;
		}
		MyString(const char* cs) : len_(strlen(cs))
		{
            init_data(cs);
			++sCtor_;
			//std::cout << "ctor called " << sCtor_  << " time(s)." << std::endl;
		}
		~MyString() noexcept
		{
			len_ = 0;
			delete[]data_; data_ = nullptr;
			++sDtor_;
			//std::cout << "dtor called " << sDtor_  << " time(s)." << std::endl;
		}

		MyString(const MyString& mstr) : len_(mstr.len_)
		{
            init_data(mstr.data_);
            ++sCopyCtor_;
			//std::cout << "CopyCtor called " << sCopyCtor_  << " time(s)." << std::endl;
		}
		MyString& operator=(const MyString& mstr)
		{
			if(this != &mstr)
			{
				if(data_)
				{
					delete [] data_; data_ = nullptr;
					len_ = 0;
				}
				len_ = mstr.len_;
            	init_data(mstr.data_);
			}
			++sCopyAsg_;
			//std::cout << "CopyAsg called " << sCopyAsg_  << " time(s)." << std::endl;

			return *this;
		}

		MyString(MyString&& mstr) noexcept
		{
			data_ = mstr.data_;
			len_ = mstr.len_;

			mstr.data_ = nullptr;
			mstr.len_ = 0;

			++sMoveCtor_;
			//std::cout << "MoveCtor called " << sMoveCtor_  << " time(s)." << std::endl;
		}
		MyString& operator=(MyString&& mstr) noexcept
		{
			if(this != &mstr)
			{
				if(data_)
				{
					delete [] data_; data_ = nullptr;
					len_ = 0;
				}

				data_ = mstr.data_;
				len_ = mstr.len_;

				mstr.data_ = nullptr;
				mstr.len_ = 0;
			}
			++sMoveAsg_;
			//std::cout << "MoveAsg called " << sMoveAsg_  << " time(s)." << std::endl;

			return *this;
		}

        // overload 'operator<' and 'operator==' for set
		bool operator<(const MyString& mstr) const
		{
            return std::string(data_) < std::string( mstr.data_);
		}
		bool operator==(const MyString& mstr) const
		{
            return std::string(data_) ==  std::string(mstr.data_);
		}

		void print() const
		{
			if(data_)
			{
				std::cout << data_ << std::endl;
			}
		}

	private:
		void init_data(const char* cs)
		{
			data_ = new char[len_+1];  // 虽然实际占用内存len_,但为了串尾的'\0'需要申请len_+1
			//memset(data_, 0, sizeof(char)*(len_+1));
			data_[len_] = '\0';
			memcpy(data_, cs, len_*sizeof(char));
		}

	public:
		static void statistics()
		{
			std::cout << "default-ctor called " << sDCtor_  << " time(s)." << std::endl;
			std::cout << "ctor called " << sCtor_  << " time(s)." << std::endl;
			std::cout << "copy-ctor called " << sCopyCtor_  << " time(s)." << std::endl;
			std::cout << "move-ctor called " << sMoveCtor_  << " time(s)." << std::endl;
			std::cout << "copy-assign called " << sCopyAsg_  << " time(s)." << std::endl;
			std::cout << "move-assign called " << sMoveAsg_  << " time(s)." << std::endl;
			//std::cout << "dtor called " << sDtor_  << " time(s)." << std::endl;
		}
		static void reset_statistics()
		{
            sDCtor_ = sCtor_ = sDtor_ = sCopyCtor_ = sCopyAsg_ = sMoveCtor_ = sMoveAsg_ = 0;
		}

	private:
		static int sDCtor_;
		static int sCtor_;
		static int sDtor_;
		static int sCopyCtor_;
		static int sCopyAsg_;
		static int sMoveCtor_;
		static int sMoveAsg_;

	private:
		char* data_;
		unsigned int len_;
	};

	int MyString::sDCtor_ = 0;
	int MyString::sCtor_ = 0;
	int MyString::sDtor_ = 0;
	int MyString::sCopyCtor_ = 0;
	int MyString::sCopyAsg_ = 0;
	int MyString::sMoveCtor_ = 0;
	int MyString::sMoveAsg_ = 0;

	enum class RLFlag {RIGHT, LEFT};

	template<typename Container>
    auto test_performance(Container &cntn, size_t times, RLFlag rl_flag) -> void
	{
		char buf[10] = {'\0'};
		using value_type = typename Container::value_type;
		//using value_type = typename std::iterator_traits<typename Container::iterator>::value_type;  // 与上句相同
		for(int i = 0; i < times; ++i)
		{
			snprintf(buf, 10, "%d", rand());
            auto it = cntn.end();
			if(RLFlag::RIGHT == rl_flag)
			{
                cntn.insert(it, value_type(buf));
			}
			else if(RLFlag::LEFT == rl_flag)
			{
                value_type val(buf);
                cntn.insert(it, val);
			}
		}
	}

	auto main() -> int
	{
		std::cout << "Testing move......" << std::endl;

#if 0		
		MyString mstr("123");
		mstr.print();

		MyString str1 = mstr;
		MyString str2 = std::move(str1);
		std::cout << std::string(20,'+') << std::endl;
		str1.print();
		std::cout << std::string(20,'+') << std::endl;

		str1 = std::move(str2);
		str1.print();
		std::cout << std::string(20,'+') << std::endl;
		str2.print();
		std::cout << std::string(20,'+') << std::endl;

		str2 = str1;
		str2.print();

		std::cout << "------------------------------" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();
		std::cout << "------------------------------" << std::endl;
输出:
123
++++++++++++++++++++
++++++++++++++++++++
123
++++++++++++++++++++
++++++++++++++++++++
123
------------------------------
default-ctor called 0 time(s).
ctor called 1 time(s).
copy-ctor called 1 time(s).
move-ctor called 1 time(s).
copy-assign called 1 time(s).
move-assign called 1 time(s).
------------------------------
#endif
	
		clock_t start_t = clock();
		std::vector<MyString> vec_r;
		test_performance(vec_r, 10000, RLFlag::RIGHT);
		clock_t end_t = clock();
		double total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "vector, RValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();
		
		std::cout << std::string(20,'-') << std::endl;
		
		start_t = clock();
		std::vector<MyString> vec_l;
		test_performance(vec_l, 10000, RLFlag::LEFT);
		end_t = clock();
		total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "vector, LValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();
		
		std::cout << std::string(40, '-') << std::endl;
		
		start_t = clock();
		std::list<MyString> lst_r;
		test_performance(lst_r, 10000, RLFlag::RIGHT);
		end_t = clock();
		total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "list, RValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();
		
		std::cout << std::string(20, '-') << std::endl;
		
		start_t = clock();
		std::list<MyString> lst_l;
		test_performance(lst_l, 10000, RLFlag::LEFT);
		end_t = clock();
		total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "list, LValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();
		
		std::cout << std::string(40, '-') << std::endl;
		
		start_t = clock();
		std::set<MyString> set_r;
		test_performance(set_r, 10000, RLFlag::RIGHT);
		end_t = clock();
		total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "set, RValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();
		
		std::cout << std::string(20, '-') << std::endl;
		
		start_t = clock();
		std::set<MyString> set_l;
		test_performance(set_l, 10000, RLFlag::LEFT);
		end_t = clock();
		total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "set, LValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();
	
		std::cout << std::string(40, '-') << std::endl;
		
		start_t = clock();
		std::deque<MyString> deque_r;
		test_performance(deque_r, 10000, RLFlag::RIGHT);
		end_t = clock();
		total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "deque, RValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();
		
		std::cout << std::string(20, '-') << std::endl;
		
		start_t = clock();
		std::deque<MyString> deque_l;
		test_performance(deque_l, 10000, RLFlag::LEFT);
		end_t = clock();
		total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "deque, LValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();
		
		std::cout << std::string(40, '-') << std::endl;
		
#if 0   // 如下操作目前示例暂不支持
		start_t = clock();
		std::map<MyString> map_r;
		test_performance(map_r, 10000, RLFlag::RIGHT);
		end_t = clock();
		total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "map, RValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();
		
		std::cout << std::string(20, '-') << std::endl;
		
		start_t = clock();
		std::map<MyString> map_l;
		test_performance(map_l, 10000, RLFlag::LEFT);
		end_t = clock();
		total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "map, LValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();

		std::cout << std::string(40, '-') << std::endl;
		
		start_t = clock();
		std::unordered_map<MyString> unomap_r;
		test_performance(unomap_r, 10000, RLFlag::RIGHT);
		end_t = clock();
		total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "unordered_map, RValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();
		
		std::cout << std::string(20, '-') << std::endl;
		
		start_t = clock();
		std::unordered_map<MyString> unomap_l;
		test_performance(unomap_l, 10000, RLFlag::LEFT);
		end_t = clock();
		total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "unordered_map, LValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();

		std::cout << std::string(40, '-') << std::endl;

		start_t = clock();
		std::unordered_set<MyString> unoset_r;
		test_performance(unoset_r, 10000, RLFlag::RIGHT);
		end_t = clock();
		total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "unordered_set, RValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();
		
		std::cout << std::string(20, '-') << std::endl;
		
		start_t = clock();
		std::unordered_set<MyString> unoset_l;
		test_performance(unoset_l, 10000, RLFlag::LEFT);
		end_t = clock();
		total_t =(double) (end_t - start_t) / CLOCKS_PER_SEC;
		std::cout << "unodered_set, LValue, time cost: " << total_t * 1000 << " ms" << std::endl;
		MyString::statistics();
		MyString::reset_statistics();
#endif

		return 0;
	}
}

输出:

__cplusplus: 201703
Testing move......
vector, RValue, time cost: 4.291 ms
default-ctor called 0 time(s).
ctor called 10000 time(s).
copy-ctor called 0 time(s).
move-ctor called 26383 time(s).  // 输出不是10000跟vector的创建方式有关系
copy-assign called 0 time(s).
move-assign called 0 time(s).
--------------------
vector, LValue, time cost: 4.493 ms
default-ctor called 0 time(s).
ctor called 10000 time(s).
copy-ctor called 10000 time(s).
move-ctor called 16383 time(s).  // 使用左值调用拷贝构造的时候,vector还是调用了异动股构造,应该也是跟vector的创建方式有关系
copy-assign called 0 time(s).
move-assign called 0 time(s).
----------------------------------------
list, RValue, time cost: 4.348 ms
default-ctor called 0 time(s).
ctor called 10000 time(s).
copy-ctor called 0 time(s).
move-ctor called 10000 time(s).
copy-assign called 0 time(s).
move-assign called 0 time(s).
--------------------
list, LValue, time cost: 4.466 ms
default-ctor called 0 time(s).
ctor called 10000 time(s).
copy-ctor called 10000 time(s).
move-ctor called 0 time(s).
copy-assign called 0 time(s).
move-assign called 0 time(s).
----------------------------------------
set, RValue, time cost: 36.106 ms
default-ctor called 0 time(s).
ctor called 10000 time(s).
copy-ctor called 0 time(s).
move-ctor called 10000 time(s).
copy-assign called 0 time(s).
move-assign called 0 time(s).
--------------------
set, LValue, time cost: 36.894 ms
default-ctor called 0 time(s).
ctor called 10000 time(s).
copy-ctor called 9999 time(s).   // 此处无法理解为啥时9999???
move-ctor called 0 time(s).
copy-assign called 0 time(s).
move-assign called 0 time(s).
----------------------------------------
deque, RValue, time cost: 3.255 ms
default-ctor called 0 time(s).
ctor called 10000 time(s).
copy-ctor called 0 time(s).
move-ctor called 10000 time(s).
copy-assign called 0 time(s).
move-assign called 0 time(s).
--------------------
deque, LValue, time cost: 3.995 ms
default-ctor called 0 time(s).
ctor called 10000 time(s).
copy-ctor called 10000 time(s).
move-ctor called 0 time(s).
copy-assign called 0 time(s).
move-assign called 0 time(s).
----------------------------------------

7.2 Item 17: Understand special member function generation

编译器可能暗自生成(generate):

  1. default ctor;
  2. copy functions(copy-ctor, copy-assignment);
  3. move functions(move-ctor, move-assignment);
  4. dtor.
  • 一旦定义dtor(意味着generated dtor不被使用,也意味着generated copy不被使用),编译器应该抑制generated copy functions(但C++98没有严格实践,C++11为了回溯相容也就没有跳出窠臼)。
  • 一旦定义dtor(意味着generated dtor不被使用,也意味着generated move不被使用),编译器应该抑制generated move functions(这一点C++11实践了,而用户代码中使用move的部分由copy functions完成)。
  • 一旦定义copy functions(意味着generated copy不被使用),就不会有generated move functions.
  • 一旦定义move functions(意味着generated move不被使用),就不会有generated copy functions.
  • 如果要让被抑制的generated functions仍然被generated,就使用=default;
  • 如果不想要generated special member functions,就使用=delete

generated(default)copy functions有什么用?用来传递member-wise copy至base partscomposited parts.
generated(default)move functions有什么用?用来传递member-wise copy至base partscomposited parts.
示例:

#pragma once

#include <iostream>
#include <string>

namespace test_move
{
	class MyString
	{
	public:
		MyString():len_(0), data_(nullptr)
		{
		    ++sDCtor_;
		}
		MyString(const char* cs) : len_(strlen(cs))
		{
            init_data(cs);
			++sCtor_;
			//std::cout << "ctor called " << sCtor_  << " time(s)." << std::endl;
		}
		virtual ~MyString() noexcept
		{
			len_ = 0;
			delete[]data_; data_ = nullptr;
			++sDtor_;
			//std::cout << "dtor called " << sDtor_  << " time(s)." << std::endl;
		}

		MyString(const MyString& mstr) : len_(mstr.len_)
		{
            init_data(mstr.data_);
            ++sCopyCtor_;
			std::cout << "CopyCtor called " << sCopyCtor_  << " time(s)." << std::endl;
		}
		MyString& operator=(const MyString& mstr)
		{
			if(this != &mstr)
			{
				if(data_)
				{
					delete [] data_; data_ = nullptr;
					len_ = 0;
				}
				len_ = mstr.len_;
            	init_data(mstr.data_);
			}
			++sCopyAsg_;
			//std::cout << "CopyAsg called " << sCopyAsg_  << " time(s)." << std::endl;

			return *this;
		}

		MyString(MyString&& mstr) noexcept
		{
			data_ = mstr.data_;
			len_ = mstr.len_;

			mstr.data_ = nullptr;
			mstr.len_ = 0;

			++sMoveCtor_;
			std::cout << "MoveCtor called " << sMoveCtor_  << " time(s)." << std::endl;
		}
		MyString& operator=(MyString&& mstr) noexcept
		{
			if(this != &mstr)
			{
				if(data_)
				{
					delete [] data_; data_ = nullptr;
					len_ = 0;
				}

				data_ = mstr.data_;
				len_ = mstr.len_;

				mstr.data_ = nullptr;
				mstr.len_ = 0;
			}
			++sMoveAsg_;
			//std::cout << "MoveAsg called " << sMoveAsg_  << " time(s)." << std::endl;

			return *this;
		}

        // overload 'operator<' and 'operator==' for set
		bool operator<(const MyString& mstr) const
		{
            return std::string(data_) < std::string( mstr.data_);
		}
		bool operator==(const MyString& mstr) const
		{
            return std::string(data_) ==  std::string(mstr.data_);
		}

		void print() const
		{
			if(data_)
			{
				std::cout << data_ << std::endl;
			}
		}

	private:
		void init_data(const char* cs)
		{
			data_ = new char[len_+1];  // 虽然实际占用内存len_,但为了串尾的'\0'需要申请len_+1
			//memset(data_, 0, sizeof(char)*(len_+1));
			data_[len_] = '\0';
			memcpy(data_, cs, len_*sizeof(char));
		}

	public:
		static void statistics()
		{
			std::cout << "default-ctor called " << sDCtor_  << " time(s)." << std::endl;
			std::cout << "ctor called " << sCtor_  << " time(s)." << std::endl;
			std::cout << "copy-ctor called " << sCopyCtor_  << " time(s)." << std::endl;
			std::cout << "move-ctor called " << sMoveCtor_  << " time(s)." << std::endl;
			std::cout << "copy-assign called " << sCopyAsg_  << " time(s)." << std::endl;
			std::cout << "move-assign called " << sMoveAsg_  << " time(s)." << std::endl;
			//std::cout << "dtor called " << sDtor_  << " time(s)." << std::endl;
		}
		static void reset_statistics()
		{
            sDCtor_ = sCtor_ = sDtor_ = sCopyCtor_ = sCopyAsg_ = sMoveCtor_ = sMoveAsg_ = 0;
		}

	private:
		static int sDCtor_;
		static int sCtor_;
		static int sDtor_;
		static int sCopyCtor_;
		static int sCopyAsg_;
		static int sMoveCtor_;
		static int sMoveAsg_;

	private:
		char* data_;
		unsigned int len_;
	};

	int MyString::sDCtor_ = 0;
	int MyString::sCtor_ = 0;
	int MyString::sDtor_ = 0;
	int MyString::sCopyCtor_ = 0;
	int MyString::sCopyAsg_ = 0;
	int MyString::sMoveCtor_ = 0;
	int MyString::sMoveAsg_ = 0;

    /* 继承一个move-aware class,Foo没有自己的move functions,
	   编译器为它生成generated default move functions,
	   default move functions会把move动作传递给base parts,
	   即MyString的move functions会被调用*/
    class Foo: public MyString{};
	
	/* 内含一个move-aware class object,Bar没有自己的move functions,
	   编译器为它生成generated default move functions,
	   default move functions会把move动作传递给composited parts,
	   即MyString的move functions会被调用*/
	class Bar
	{
    private:
			MyString str_;
	};

	class Goo
	{
	public:
		~Goo(){} // 定义dtor,抑制了generated move functions, 但generated copy functions仍然存在,,则move由copy代替 
	private:
		MyString str_;
	};

	auto main() -> int
	{
		std::cout << "Testing move......" << std::endl;

		Foo f1;
		Bar b1;
		Goo g1;

		Foo f2 = f1;
		Bar b2 = b1;

        std::cout << std::string(30, '-') << std::endl;

		Foo f3 = std::move(f1);
		Bar b3 = std::move(b1);

        std::cout << std::string(30, '-') << std::endl;
        
		Goo g2 = g1;
		Goo g3 = std::move(g1);

		return 0;
	}
}

输出:

Testing move......
CopyCtor called 1 time(s).
CopyCtor called 2 time(s).
------------------------------
MoveCtor called 1 time(s).
MoveCtor called 2 time(s).
------------------------------
CopyCtor called 3 time(s).
CopyCtor called 4 time(s).

7.3 左/右值参数传递引发的动作

  • pass by value,左值引发copy-ctor; 右值引发move-ctor
  • pass by reference不引发任何特殊函数
  • 右值不能被左值引用绑定,但可被左值const引用绑定
  • 左值不能被右值引用绑定,不管是不是const引用
  • 左右值都可以被universal-reference绑定
  1. 拷贝构造、拷贝赋值和移动构造、移动赋值都存在时在函数调用时引发的操作。
#pragma once

#include <iostream>
#include <string>

namespace test_move
{
	class MyString
	{
	public:
		MyString():len_(0), data_(nullptr)
		{
		    ++sDCtor_;
		}
		MyString(const char* cs) : len_(strlen(cs))
		{
            init_data(cs);
			++sCtor_;
			std::cout << "ctor called " << sCtor_  << " time(s)." << std::endl;
		}
		virtual ~MyString() noexcept
		{
			len_ = 0;
			delete[]data_; data_ = nullptr;
			++sDtor_;
	        std::cout << "dtor called " << sDtor_  << " time(s)." << std::endl;
		}

		MyString(const MyString& mstr) : len_(mstr.len_)
		{
            init_data(mstr.data_);
            ++sCopyCtor_;
			std::cout << "CopyCtor called " << sCopyCtor_  << " time(s)." << std::endl;
		}
		MyString& operator=(const MyString& mstr)
		{
			if(this != &mstr)
			{
				if(data_)
				{
					delete [] data_; data_ = nullptr;
					len_ = 0;
				}
				len_ = mstr.len_;
            	init_data(mstr.data_);
			}
			++sCopyAsg_;
			std::cout << "CopyAsg called " << sCopyAsg_  << " time(s)." << std::endl;

			return *this;
		}

		MyString(MyString&& mstr) noexcept
		{
			data_ = mstr.data_;
			len_ = mstr.len_;

			mstr.data_ = nullptr;
			mstr.len_ = 0;

			++sMoveCtor_;
			std::cout << "MoveCtor called " << sMoveCtor_  << " time(s)." << std::endl;
		}
		MyString& operator=(MyString&& mstr) noexcept
		{
			if(this != &mstr)
			{
				if(data_)
				{
					delete [] data_; data_ = nullptr;
					len_ = 0;
				}

				data_ = mstr.data_;
				len_ = mstr.len_;

				mstr.data_ = nullptr;
				mstr.len_ = 0;
			}
			++sMoveAsg_;
			std::cout << "MoveAsg called " << sMoveAsg_  << " time(s)." << std::endl;

			return *this;
		}

        // overload 'operator<' and 'operator==' for set
		bool operator<(const MyString& mstr) const
		{
            return std::string(data_) < std::string( mstr.data_);
		}
		bool operator==(const MyString& mstr) const
		{
            return std::string(data_) ==  std::string(mstr.data_);
		}

		void print() const
		{
			if(data_)
			{
				std::cout << data_ << std::endl;
			}
		}

	private:
		void init_data(const char* cs)
		{
			data_ = new char[len_+1];  // 虽然实际占用内存len_,但为了串尾的'\0'需要申请len_+1
			//memset(data_, 0, sizeof(char)*(len_+1));
			data_[len_] = '\0';
			memcpy(data_, cs, len_*sizeof(char));
		}

	public:
		static void statistics()
		{
			std::cout << "default-ctor called " << sDCtor_  << " time(s)." << std::endl;
			std::cout << "ctor called " << sCtor_  << " time(s)." << std::endl;
			std::cout << "copy-ctor called " << sCopyCtor_  << " time(s)." << std::endl;
			std::cout << "move-ctor called " << sMoveCtor_  << " time(s)." << std::endl;
			std::cout << "copy-assign called " << sCopyAsg_  << " time(s)." << std::endl;
			std::cout << "move-assign called " << sMoveAsg_  << " time(s)." << std::endl;
			//std::cout << "dtor called " << sDtor_  << " time(s)." << std::endl;
		}
		static void reset_statistics()
		{
            sDCtor_ = sCtor_ = sDtor_ = sCopyCtor_ = sCopyAsg_ = sMoveCtor_ = sMoveAsg_ = 0;
		}

	private:
		static int sDCtor_;
		static int sCtor_;
		static int sDtor_;
		static int sCopyCtor_;
		static int sCopyAsg_;
		static int sMoveCtor_;
		static int sMoveAsg_;

	private:
		char* data_;
		unsigned int len_;
	};

	int MyString::sDCtor_ = 0;
	int MyString::sCtor_ = 0;
	int MyString::sDtor_ = 0;
	int MyString::sCopyCtor_ = 0;
	int MyString::sCopyAsg_ = 0;
	int MyString::sMoveCtor_ = 0;
	int MyString::sMoveAsg_ = 0;

	void f1(MyString){std::cout << "f1, pass by value." << std::endl;}
	void f2(MyString&){std::cout << "f2, pass by reference." << std::endl;}
	void f3(const MyString&){std::cout << "f3, pass by const-reference." << std::endl;}
	void f4(MyString&&){std::cout << "f4, pass by right-value-reference." << std::endl;}
	void f5(const MyString&&){std::cout << "f5, pass by const-right-value-reference." << std::endl;}
	template<typename T>
	void f6(T&&){std::cout << "f6, pass by universal-reference." << std::endl; }

	auto main() -> int
	{
		std::cout << "Testing move......" << std::endl;

		MyString mstr("12345");

        f1(mstr);            // 引发拷贝构造函数
		f1(MyString("789")); // 引发构造函数
		f1(std::move(mstr)); // 引发移动构造函数

		f2(mstr);
		// f2(MyString("abc"));  // error: no matching function for call to 'f2'. candidate function not viable: expects an l-value for 1st argument

		f3(mstr);            // 未引发任何构造函数
		f3(MyString(",.?")); // 引发构造函数
		f3(std::move(mstr)); // 未引发任何构造函数

		// f4(mstr);  // error: no matching function for call to 'f4'. candidate function not viable: no known conversion from 'test_move::MyString' to 'test_move::MyString &&' for 1st argument
		f4(MyString("abcd"));

		// f5(mstr); // error: no matching function for call to 'f5'. candidate function not viable: no known conversion from 'test_move::MyString' to 'const test_move::MyString &&' for 1st argument
		f5(MyString("-=[]"));
	
		f6(mstr);
		f6(std::move(mstr));

		return 0;
	}
}

输出:

ctor called 1 time(s).
CopyCtor called 1 time(s).
f1, pass by value.
dtor called 1 time(s).
ctor called 2 time(s).
f1, pass by value.
dtor called 2 time(s).
MoveCtor called 1 time(s).
f1, pass by value.
dtor called 3 time(s).
f2, pass by reference.
f3, pass by const-reference.
ctor called 3 time(s).
f3, pass by const-reference.
dtor called 4 time(s).
f3, pass by const-reference.
ctor called 4 time(s).
f4, pass by right-value-reference.
dtor called 5 time(s).
ctor called 5 time(s).
f5, pass by const-right-value-reference.
dtor called 6 time(s).
f6, pass by universal-reference.
f6, pass by universal-reference.
dtor called 7 time(s).
  1. 仅有拷贝构造、拷贝赋值时(移动构造、移动赋值均delete)在函数调用时引发的操作。
#include <iostream>
#include <string>

namespace test_move
{
	class Foo
	{
	public:
		Foo(){std::cout << "Foo ctor called." << std::endl;}
		virtual ~Foo(){std::cout << "Foo dtor called." << std::endl;}
		Foo(const Foo&){std::cout << "Foo copy-ctor called." << std::endl;}
		Foo& operator=(const Foo&){std::cout << "Foo copy-assignment called." << std::endl; return *this;}
		Foo(Foo&&) = delete;
		Foo& operator=(Foo&&) = delete;
	};

	void f1(Foo){std::cout << "f1, pass by value." << std::endl;}
	void f2(Foo&){std::cout << "f2, pass by reference." << std::endl;}
	void f3(const Foo&){std::cout << "f3, pass by const-reference." << std::endl;}
	void f4(Foo&&){std::cout << "f4, pass by right-value-reference." << std::endl;}
	void f5(const Foo&&){std::cout << "f5, pass by const-right-value-reference." << std::endl;}
	template<typename T>
	void f6(T&&){std::cout << "f6, pass by universal-reference." << std::endl; }

	auto main() -> int
	{
		std::cout << "Testing move......" << std::endl;

		Foo foo;

        f1(foo);               // 引发拷贝构造函数
		f1(Foo());             // 引发构造函数
		// f1(std::move(foo)); // error C2280: “test_move::Foo::Foo(test_move::Foo &&)”: 尝试引用已删除的函数

		f2(foo);
		f2(Foo());             // 引发构造函数

		f3(foo);               // 未引发任何构造函数
		f3(Foo());             // 引发构造函数
		f3(std::move(foo));    // 未引发任何构造函数

		//f4(foo);             // error: 无法将左值绑定到右值引用
		f4(Foo());             // 引发构造函数
				               
		//f5(foo);             // error: 无法将左值绑定到右值引用
		f5(Foo());             // 引发构造函数
	
		f6(foo);
		f6(std::move(foo));    // 未引发任何构造函数

		return 0;
	}
}

输出:

Testing move......
Foo ctor called.
Foo copy-ctor called.
f1, pass by value.
Foo dtor called.
Foo ctor called.
f1, pass by value.
Foo dtor called.
f2, pass by reference.
Foo ctor called.
f2, pass by reference.
Foo dtor called.
f3, pass by const-reference.
Foo ctor called.
f3, pass by const-reference.
Foo dtor called.
f3, pass by const-reference.
Foo ctor called.
f4, pass by right-value-reference.
Foo dtor called.
Foo ctor called.
f5, pass by const-right-value-reference.
Foo dtor called.
f6, pass by universal-reference.
f6, pass by universal-reference.
Foo dtor called.
  1. 仅有移动构造、移动赋值存在时(拷贝构造、拷贝赋值均delete)在函数调用时引发的操作。
#include <iostream>
#include <string>

namespace test_move
{
	class Foo
	{
	public:
		Foo(){std::cout << "Foo ctor called." << std::endl;}
		virtual ~Foo(){std::cout << "Foo dtor called." << std::endl;}
		Foo(const Foo&) = delete;
		Foo& operator=(const Foo&) = delete;
		Foo(Foo&&){std::cout << "Foo move-ctor called." << std::endl;}
		Foo& operator=(Foo&&){std::cout << "Foo copy-assignment called." << std::endl; return *this;}
	};

	void f1(Foo){std::cout << "f1, pass by value." << std::endl;}
	void f2(Foo&){std::cout << "f2, pass by reference." << std::endl;}
	void f3(const Foo&){std::cout << "f3, pass by const-reference." << std::endl;}
	void f4(Foo&&){std::cout << "f4, pass by right-value-reference." << std::endl;}
	void f5(const Foo&&){std::cout << "f5, pass by const-right-value-reference." << std::endl;}
	template<typename T>
	void f6(T&&){std::cout << "f6, pass by universal-reference." << std::endl; }

	auto main() -> int
	{
		std::cout << "Testing move......" << std::endl;

		Foo foo;

        //f1(foo);               // error C2280: “test_move::Foo::Foo(const test_move::Foo &)”: 尝试引用已删除的函数 
		f1(Foo());             // 引发构造函数
		f1(std::move(foo));    // 引发移动过构造函数

		f2(foo);
		f2(Foo());             // 引发构造函数

		f3(foo);               // 未引发任何构造函数
		f3(Foo());             // 引发构造函数
		f3(std::move(foo));    // 未引发任何构造函数

		//f4(foo);             // error: 无法将左值绑定到右值引用
		f4(Foo());             // 引发构造函数
				               
		//f5(foo);             // error: 无法将左值绑定到右值引用
		f5(Foo());             // 引发构造函数
	
		f6(foo);               // 未引发任何构造函数
		f6(std::move(foo));    // 未引发任何构造函数

		return 0;
	}
}

输出:

Testing move......
Foo ctor called.
Foo ctor called.
f1, pass by value.
Foo dtor called.
Foo move-ctor called.
f1, pass by value.
Foo dtor called.
f2, pass by reference.
Foo ctor called.
f2, pass by reference.
Foo dtor called.
f3, pass by const-reference.
Foo ctor called.
f3, pass by const-reference.
Foo dtor called.
f3, pass by const-reference.
Foo ctor called.
f4, pass by right-value-reference.
Foo dtor called.
Foo ctor called.
f5, pass by const-right-value-reference.
Foo dtor called.
f6, pass by universal-reference.
f6, pass by universal-reference.
Foo dtor called.

Reference

C++ Primer(第5版)
Effective Modern C++

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值