容器中应用三五法则和移动语义的效率探究

来源:互联网 发布:qq飞车暗夜幽灵数据 编辑:程序博客网 时间:2024/05/20 02:51

  • 何谓三五法则
  • 探究三五法则
    • 首先给出一个基本类定义
      • 类代码
    • 测试 A在 vector 容器中应用三五法则和移动语义的效率分析
      • 测试代码
      • 结论
    • 测试 B在 vector 容器中应用复杂自定义类型的三五法则和移动语义的效率分析
      • 类代码
      • 测试代码
      • 结论
    • 测试 C在函数中应用自定义类型的三五法则和移动语义的效率分析
      • 函数定义代码
      • 测试代码
      • 结论
    • 测试 D类中成员变量是引用类型
      • 类代码
      • 测试代码
      • 结论
    • 后记


何谓三/五法则

在《C++ Primer》中提出了三/五法则(P447 第五版)

直接引用前辈总结的内容吧:

原文

  • 需要析构函数的类也需要拷贝构造函数和拷贝赋值函数
  • 需要拷贝操作的类也需要赋值操作,反之亦然
  • 析构函数不能是删除的
  • 如果一个类有删除的或不可访问的析构函数,那么其默认和拷贝构造函数会被定义为删除的
  • 如果一个类有const或引用成员,则不能使用合成的拷贝赋值操作

探究三五法则

首先给出一个基本类定义

该基本类包含:
- 空构造
- 拷贝构造
- 拷贝赋值
- 移动构造
- 移动赋值
- 析构函数
- 内置类型成员变量

类代码

class ExampleClass {public:    // empty constructor    ExampleClass() {        m_x = 0;    }    // constructor    ExampleClass(int x) : m_x(x) {        cout << "ExampleClass constructor is called" << endl;    }    // copy constructor    ExampleClass(const ExampleClass& example) {        cout << "ExampleClass copy constructor is called" << endl;        m_x = example.m_x;    }    // copy assign operator    ExampleClass& operator= (const ExampleClass& example) {        cout << "ExampleClass copy assign operator is called" << endl;        if(this != &example) {            this->m_x = example.m_x;        }        return *this;    }    // move constructor    ExampleClass(ExampleClass&& example) {        cout << "ExampleClass move constructor is called" << endl;        m_x = example.m_x;    }    // move assign operator    ExampleClass& operator= (ExampleClass&& example) {        cout << "ExampleClass move assign operator is called" << endl;        if(this != &example) {            this->m_x = example.m_x;        }        return *this;    }    // destructor    ~ExampleClass() {        cout << "ExampleClass destructor is called" << endl;    }    int m_x;};

测试 A:在 vector 容器中应用三五法则和移动语义的效率分析

使用这个类,假如我有一个 vector 容器

测试代码

// functionA: 左值版本 push_back// void push_back (const value_type& val);// functionB: 右值版本 push_back// void push_back (value_type&& val);// functionC: 右值版本 emplace_back// template <class... Args>// void emplace_back (Args&&... args);void TestA() {    vector<ExampleClass> exampleClassVector;    cout << "use lvalue:" << endl;    cout << "create lvalue example class object and push back" << endl;    ExampleClass ec(1);    exampleClassVector.push_back(ec);    exampleClassVector.clear();    cout << "result: push back lvalue directly will use functionA" << endl;    cout << "create lvalue example class object and push back by std::move" << endl;    ExampleClass ec1(3);    exampleClassVector.push_back(std::move(ec1));    exampleClassVector.clear();    cout << "result: if there has move constructor, then use functionB else use functionA" << endl;    cout << "conclusion: lvalue should consider use std::move to push_back firstly" << endl;    cout << "use rvalue:" << endl;    cout << "create rvalue example class object and push back" << endl;    exampleClassVector.push_back(ExampleClass(2));    exampleClassVector.clear();    cout << "result: push back rvalue directly will use functionB" << endl;    cout << "create rvalue example class object and push back by std::move" << endl;    exampleClassVector.push_back(std::move(ExampleClass(4)));    exampleClassVector.clear();    cout << "result: use functionB, same as push back rvalue directly" << endl;    cout << "create rvalue example class object by emplace back" << endl;    exampleClassVector.emplace_back(5);    exampleClassVector.clear();    cout << "result: use emplace_back to construct here" << endl;    cout << "create rvalue example class object and emplace back" << endl;    exampleClassVector.emplace_back(ExampleClass(6));    exampleClassVector.clear();    cout << "result: no need to do this" << endl;    cout << "conclusion: rvalue should consider use emplace back firstly" << endl;    cout << "end of test" << endl;}

结论

1、左值应该首先考虑用 std::move 添加至容器
2、右值应该首先考虑使用 emplace_back 添加至容器


测试 B:在 vector 容器中应用复杂自定义类型的三五法则和移动语义的效率分析

考虑更加复杂的情况:
含自定义类型的成员变量的类

类代码

class ExampleClassContainer {public:    // empty constructor    ExampleClassContainer() {        cout << "ExampleClassContainer empty constructor is called" << endl;    }    // constructor: parameter use ExampleClass's copy constructor 2 times    // ExampleClassContainer(ExampleClass exampleClass) : m_exampleClass(exampleClass) {    //     cout << "ExampleClassContainer constructor is called, with copy parameter" << endl;    // }    // constructor: parameter use ExampleClass's copy constructor 1 time    ExampleClassContainer(ExampleClass exampleClass) {        cout << "ExampleClassContainer constructor is called, with copy parameter" << endl;        m_exampleClass = std::move(exampleClass); // use ExampleClass's move assign operator    }    // copy constructor    ExampleClassContainer(const ExampleClassContainer& exampleClassContainer) : m_exampleClass(exampleClassContainer.m_exampleClass) {        cout << "ExampleClassContainer copy constructor is called" << endl;    }    // copy assign operator    ExampleClassContainer& operator= (const ExampleClassContainer& exampleClassContainer) {        cout << "ExampleClassContainer copy assign operator is called" << endl;        if(this != &exampleClassContainer) {            this->m_exampleClass = exampleClassContainer.m_exampleClass;        }        return *this;    }    // move constructor    ExampleClassContainer(ExampleClassContainer&& exampleClassContainer) /* : m_exampleClass(exampleClassContainer.m_exampleClass) use ExampleClass's copy constructor*/ {        cout << "ExampleClassContainer move constructor is called" << endl;        std::swap(m_exampleClass, exampleClassContainer.m_exampleClass); // use ExampleClass's move constructor and move assign operator 2 times    }    // move assign operator    ExampleClassContainer& operator= (ExampleClassContainer&& exampleClassContainer) {        cout << "ExampleClassContainer move assign operator is called" << endl;        if(this != &exampleClassContainer) {            // m_exampleClass = exampleClassContainer.m_exampleClass; // use ExampleClass's copy assign operator            std::swap(m_exampleClass, exampleClassContainer.m_exampleClass); // use ExampleClass's move constructor and move assign operator 2 times        }        return *this;    }    ExampleClass m_exampleClass;};

测试代码

void TestB() {    cout << "use lvalue:" << endl;    cout << "create lvalue ExampleClass object" << endl;    ExampleClass exampleClassA(1);    cout << "create an ExampleClassContainer object by lvalue" << endl;    ExampleClassContainer exampleClassContainerA(exampleClassA);    cout << "create lvalue ExampleClassContainer object by std::move lvalue ExampleClass object" << endl;    ExampleClassContainer exampleClassContainerB(std::move(exampleClassA));    cout << "create lvalue ExampleClassContainer object by rvalue ExampleClass object" << endl;    ExampleClassContainer exampleClassContainerC(ExampleClass(2));    cout << "create lvalue ExampleClassContainer object by copy constructor" << endl;    ExampleClassContainer exampleClassContainerD(exampleClassContainerC);    cout << "assign lvalue ExampleClassContainer object by copy assign operator" << endl;    exampleClassContainerD = exampleClassContainerB;    cout << "create lvalue ExampleClassContainer object by move constructor" << endl;    ExampleClassContainer exampleClassContainerE(std::move(exampleClassContainerA));    cout << "assign lvalue ExampleClassContainer object by move assign operator" << endl;    exampleClassContainerE = std::move(exampleClassContainerD);    cout << "conclusion: move or copy has nothing to do with combination" << endl;    cout << "end of test" << endl;}

结论

移动或拷贝对复杂的组合类型没有影响


测试 C:在函数中应用自定义类型的三五法则和移动语义的效率分析

我们看看在传递参数的时候的情况

函数定义代码

/** * 值传递 * @param exampleClass 拷贝构造,右值无消耗 */void Func1(ExampleClass exampleClass) {    cout << "Func1" << endl;}/** * 左值引用传递 * @param exampleClass 左值无消耗 */void Func2(const ExampleClass& exampleClass) {    cout << "Func2" << endl;}/** * 右值引用传递 * @param exampleClass 右值无消耗 */void Func3(ExampleClass&& exampleClass) {    cout << "Func3" << endl;}/** * 右值引用传递,值传递返回 * @param  example 右值无消耗 * @return         值传递 */ExampleClass Func4(ExampleClass&& example) {    cout << "Func4" << endl;    return example;}/** * 右值引用传递,引用传递返回 * @param  example 右值无消耗 * @return         引用传递 */ExampleClass& Func5(ExampleClass&& example) {    cout << "Func5" << endl;    return example;}/** * 值传递返回 * @return 左值 */ExampleClass Func6() {    ExampleClass exampleClass(1);    cout << "Func6" << endl;    return exampleClass;}/** * 值传递返回 * @return 右值 */ExampleClass Func7() {    cout << "Func7" << endl;    return ExampleClass(1);}/** * 右值引用传递返回 * @return std::move 左值 */ExampleClass&& Func8() {    ExampleClass exampleClass(1);    cout << "Func8" << endl;    return std::move(exampleClass);}/** * 右值引用传递返回 * @return 右值 */ExampleClass&& Func9() {    cout << "Func9" << endl;    return ExampleClass(1);}

测试代码

/** * 每个对象只应该调用一个 constructor 和 destructor * 多余的 constructor 和 destructor 都是额外消耗 */void TestC() {    cout << "create lvalue ExampleClass object" << endl;    ExampleClass exampleClassA(1);    cout << "use lvalue as lvalue parameter" << endl;    Func1(exampleClassA); // cost: copy constructor, destructor    cout << "use std::move lvalue as lvalue parameter" << endl;    Func1(std::move(exampleClassA)); // cost: move constructor, destructor    cout << "use rvalue as lvalue parameter" << endl;    Func1(ExampleClass(2)); // no cost    cout << "use lvalue as lvalue reference" << endl;    Func2(exampleClassA); // no cost    cout << "use rvalue as lvalue reference" << endl;    Func2(ExampleClass(3)); // no cost    cout << "use lvalue as rvalue reference parameter by using std::move" << endl;    Func3(std::move(exampleClassA)); // no cost    cout << "use rvalue as rvalue reference parameter" << endl;    Func3(ExampleClass(4)); // no cost    cout << "use lvalue as rvalue reference parameter by using std::move and return value" << endl;    ExampleClass exampleClassB = Func4(std::move(exampleClassA)); // cost: copy constructor    cout << "use lvalue as rvalue reference parameter by using std::move and return reference" << endl;    auto& exampleClassC = Func5(std::move(exampleClassA)); // no cost    ExampleClass& exampleClassD = Func5(std::move(exampleClassA)); // no cost    cout << "value return lvalue" << endl;    ExampleClass exampleClassE = Func6(); // no cost    cout << "value return rvalue" << endl;    ExampleClass exampleClassF = Func7(); // no cost    cout << "rvalue reference return std::move lvalue" << endl;    ExampleClass&& exampleClassG = Func8(); // cost: destructor    // cout << "rvalue reference return rvalue" << endl;    // auto exampleClassH = Func9(); // 返回了一个临时变量的右值引用,bug    cout << "end of test" << endl;}

结论

注意无消耗的那几条语句


测试 D:类中成员变量是引用类型

当成员变量是引用类型的时候

类代码

class ExampleClassContainerRef {public:    // // empty constructor is invalid    // ExampleClassContainerRef() {    //    // }    // constructor should init mr_exampleClass: parameter use ExampleClass's copy constructor 1 time    ExampleClassContainerRef(ExampleClass exampleClass) : mr_exampleClass(exampleClass) {        cout << "ExampleClassContainerRef constructor is called" << endl;    }    // copy constructor should init mr_exampleClass:    ExampleClassContainerRef(const ExampleClassContainerRef& exampleClassContainerRef) : mr_exampleClass(exampleClassContainerRef.mr_exampleClass) {        cout << "ExampleClassContainerRef copy constructor is called" << endl;    }    // copy assign operator    ExampleClassContainerRef& operator= (const ExampleClassContainerRef& exampleClassContainerRef) {        cout << "ExampleClassContainerRef copy assign operator is called" << endl;        if(this != &exampleClassContainerRef) {            this->mr_exampleClass = exampleClassContainerRef.mr_exampleClass;        }        return *this;    }    // move constructor    ExampleClassContainerRef(ExampleClassContainerRef&& exampleClassContainerRef) : mr_exampleClass(exampleClassContainerRef.mr_exampleClass) {        cout << "ExampleClassContainerRef move constructor is called" << endl;    }    // move assign operator    ExampleClassContainerRef& operator= (ExampleClassContainerRef&& exampleClassContainerRef) {        cout << "ExampleClassContainerRef move assign operator is called" << endl;        if(this != &exampleClassContainerRef) {            // this->mr_exampleClass = exampleClassContainerRef.mr_exampleClass;            std::swap(mr_exampleClass, exampleClassContainerRef.mr_exampleClass); // use ExampleClass's move constructor and move assign 2 times        }        return *this;    }    // destructor    ~ExampleClassContainerRef() {        cout << "ExampleClassContainerRef destructor is called" << endl;    }    ExampleClass& mr_exampleClass;};

测试代码

void TestD() {    cout << "use lvalue:" << endl;    cout << "create lvalue ExampleClass object" << endl;    ExampleClass exampleClassA(1);    cout << "exampleClassA address:" << &exampleClassA << endl;    cout << "create an ExampleClassContainerRef object by lvalue" << endl;    ExampleClassContainerRef exampleClassContainerRefA(exampleClassA);    cout << "exampleClassContainerRefA.mr_exampleClass address:" << &exampleClassContainerRefA.mr_exampleClass << endl;    cout << "create lvalue ExampleClassContainerRef object by std::move lvalue ExampleClass object" << endl;    ExampleClassContainerRef exampleClassContainerRefB(std::move(exampleClassA));    cout << "exampleClassContainerRefB.mr_exampleClass address:" << &exampleClassContainerRefB.mr_exampleClass << endl;    cout << "create lvalue ExampleClassContainerRef object by rvalue ExampleClass object" << endl;    ExampleClassContainerRef exampleClassContainerRefC(ExampleClass(2));    cout << "exampleClassContainerRefC.mr_exampleClass address:" << &exampleClassContainerRefC.mr_exampleClass << endl;    cout << "create lvalue ExampleClassContainerRef object by copy constructor" << endl;    ExampleClassContainerRef exampleClassContainerRefD(exampleClassContainerRefC); // QUESTION: ExampleClass is no called?    cout << "exampleClassContainerRefD.mr_exampleClass address:" << &exampleClassContainerRefD.mr_exampleClass << endl; // NOTE: no cost    cout << "assign lvalue ExampleClassContainerRef object by copy assign operator" << endl;    exampleClassContainerRefD = exampleClassContainerRefB;    cout << "exampleClassContainerRefD.mr_exampleClass address:" << &exampleClassContainerRefD.mr_exampleClass << endl;    cout << "create lvalue ExampleClassContainerRef object by move constructor" << endl;    ExampleClassContainerRef exampleClassContainerRefE(std::move(exampleClassContainerRefA)); // QUESTION: ExampleClass is no called?    cout << "exampleClassContainerRefE.mr_exampleClass address:" << &exampleClassContainerRefE.mr_exampleClass << endl; // NOTE: no cost    cout << "assign lvalue ExampleClassContainerRef object by move assign operator" << endl;    exampleClassContainerRefE = std::move(exampleClassContainerRefD);    cout << "end of test" << endl;}

结论

注意无消耗的那几条语句


后记

以前一时兴起之作,后因年代久远,忘记探究初衷,将就着看吧,探究一下三五法则和拷贝移动语义在实际程序中发生了什么不是很有趣吗?尤其是那几个看上去是零消耗的操作


CSDN 辣鸡 MD 编辑器,无序列表格式全丢

原创粉丝点击